@ant-design/icons#RightOutlined TypeScript Examples

The following examples show how to use @ant-design/icons#RightOutlined. 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: ProviderGroupMenu.tsx    From next-basics with GNU General Public License v3.0 6 votes vote down vote up
export function ProviderGroupMenu(
  props: ProviderGroupMenuProps
): React.ReactElement {
  const { itemList, onFold, containerStyle = {} } = props;
  const [fold, setFold] = useState(false);

  const handleClick = (): void => {
    const curFold = !fold;
    setFold(curFold);
    onFold?.(curFold);
  };

  return (
    <div style={{ position: "relative" }}>
      <span
        onClick={handleClick}
        className={classNames(styles.customIcon, { [styles.fasten]: fold })}
      >
        {fold ? <LeftOutlined /> : <RightOutlined />}
      </span>

      <div
        className={styles.container}
        style={{
          display: fold ? "none" : "block",
          ...containerStyle,
        }}
      >
        {itemList?.map((name) => (
          <div key={name} className={styles.itemWrapper} title={name}>
            <HashLink to={`#${name}`} className={styles.item}>
              {name}
            </HashLink>
          </div>
        ))}
      </div>
    </div>
  );
}
Example #2
Source File: Panel.tsx    From fe-v5 with Apache License 2.0 6 votes vote down vote up
export default function Panel(props: IProps) {
  const [isActive, setIsActive] = useState<boolean>(props.isActive || true);
  return (
    <div
      className={classnames({
        'n9e-collapse-item': true,
        'n9e-collapse-item-active': isActive,
        'n9e-collapse-item-inner': props.isInner,
      })}
    >
      <div
        className='n9e-collapse-header'
        onClick={() => {
          setIsActive(!isActive);
        }}
      >
        {isActive ? <DownOutlined className='n9e-collapse-arrow' /> : <RightOutlined className='n9e-collapse-arrow' />}
        {props.header}
        <div
          className='n9e-collapse-extra'
          onClick={(e) => {
            e.stopPropagation();
            e.nativeEvent.stopImmediatePropagation();
          }}
        >
          {props.extra}
        </div>
      </div>
      <div
        className={classnames({
          'n9e-collapse-content': true,
          'n9e-collapse-content-hidden': !isActive,
        })}
      >
        <div className='n9e-collapse-content-box'>{props.children}</div>
      </div>
    </div>
  );
}
Example #3
Source File: DesktopSideNav.tsx    From condo with MIT License 5 votes vote down vote up
DesktopSideNav: React.FC<ISideNavProps> = (props) => {
    const { onLogoClick, menuData } = props
    const { link } = useOrganization()
    const { isSmall, toggleCollapsed, isCollapsed } = useLayoutContext()

    const isEmployeeBlocked = get(link, 'isBlocked', false)

    if (isEmployeeBlocked) {
        return null
    }

    // TODO: (Dimitreee) implement mobile nav later
    if (isSmall) {
        return null
    }

    return (
        <>
            <Layout.Sider
                collapsed={isCollapsed}
                theme='light'
                css={SIDE_NAV_STYLES}
                width={SIDE_MENU_WIDTH}
                collapsedWidth={COLLAPSED_SIDE_MENU_WIDTH}
            >
                <LogoContainer>
                    <Logo onClick={onLogoClick} minified={isCollapsed}/>
                </LogoContainer>
                <LayoutTriggerWrapper>
                    <Button
                        onClick={toggleCollapsed}
                        size={'small'}
                        shape={'circle'}
                        icon={isCollapsed ? <RightOutlined style={{ fontSize: '13px' }} /> : <LeftOutlined style={{ fontSize: '13px' }}/>}
                    />
                </LayoutTriggerWrapper>
                <ActionsContainer minified={isCollapsed}>
                    <ResidentActions minified={isCollapsed}/>
                </ActionsContainer>
                <MenuItemsContainer>
                    {menuData}
                </MenuItemsContainer>
                <ServiceSubscriptionIndicator/>
            </Layout.Sider>
            <Layout.Sider
                collapsed={isCollapsed}
                width={SIDE_MENU_WIDTH}
                collapsedWidth={COLLAPSED_SIDE_MENU_WIDTH}
            />
        </>
    )
}
Example #4
Source File: StatsCard.tsx    From condo with MIT License 5 votes vote down vote up
StatsCard: React.FC<IStatsCardProps> = (props) => {
    const intl = useIntl()
    const extraTitle = intl.formatMessage({ id: 'component.statscard.ExtraTitle' })
    const SELECTED_PERIOD: SelectedPeriod = {
        calendarWeek: intl.formatMessage({ id: 'component.statscard.periodtypes.Week' }),
        month: intl.formatMessage({ id: 'component.statscard.periodtypes.Month' }),
        quarter: intl.formatMessage({ id: 'component.statscard.periodtypes.Quarter' }),
        year: intl.formatMessage({ id: 'component.statscard.periodtypes.Year' }),
    }

    const { title, children, link, loading = false, onFilterChange, dependencyArray } = props

    const [selectedPeriod, setSelectedPeriod] = useState<string>(Object.keys(SELECTED_PERIOD)[0])
    const updateDependencies = [selectedPeriod, ...dependencyArray]

    useEffect(() => {
        onFilterChange(selectedPeriod)
    }, updateDependencies)

    const menuClick = useCallback(({ key }) => { setSelectedPeriod(key)}, [])
    const linkClick = useCallback(() => { Router.push(link) }, [link])

    const menuOverlay = (
        <Menu onClick={menuClick} disabled={loading}>
            {
                Object.keys(SELECTED_PERIOD).map((period) => (
                    <Menu.Item key={period}>{SELECTED_PERIOD[period]}</Menu.Item>
                ))
            }
        </Menu>
    )

    const cardTitle = (
        <Space css={cardTitleCss}>
            {title}
            <Dropdown overlay={menuOverlay} >
                <span style={DROPDOWN_TEXT_STYLE}>{SELECTED_PERIOD[selectedPeriod]} <DownOutlined /></span>
            </Dropdown>
        </Space>
    )

    const cardExtra = (
        <Button style={CARD_EXTRA_STYLE} type={'inlineLink'} onClick={linkClick}>
            {extraTitle}{<RightOutlined />}
        </Button>
    )

    return (
        <Row gutter={STATS_CARD_ROW_GUTTER} align={'middle'}>
            <Col span={24}>
                <Card
                    title={cardTitle}
                    bordered={false}
                    headStyle={CARD_HEAD_STYLE}
                    extra={cardExtra}
                >
                    {loading ? <Skeleton active round paragraph={{ rows: 1 }} /> : children}
                </Card>
            </Col>
        </Row>
    )
}
Example #5
Source File: Pagination.tsx    From wildduck-ui with MIT License 5 votes vote down vote up
Pagination: React.FC<IPagination> = ({
	limit,
	previous,
	next,
	page,
	setLimit,
	setPrevious,
	setNext,
	setPage,
}: IPagination) => {
	return (
		<Space>
			<Tooltip title={'Previous'}>
				<Button
					className='ant-btn-icon'
					disabled={_.isEmpty(previous)}
					onClick={() => {
						setPrevious(previous);
						setPage(Math.max(page - 1, 1));
					}}
				>
					<LeftOutlined className='blue-color' />
				</Button>
			</Tooltip>
			<Select
				style={{ width: 120 }}
				defaultValue={limit}
				onSelect={(value) => {
					setLimit(value);
				}}
			>
				{_.map([10, 20, 30, 50, 100, 250], (value) => (
					<Select.Option key={value} value={value}>
						{value + ' / page'}
					</Select.Option>
				))}
			</Select>
			<Tooltip title={'Next'}>
				<Button
					className='ant-btn-icon'
					disabled={_.isEmpty(next)}
					onClick={() => {
						setNext(next);
						setPage(page + 1);
					}}
				>
					<RightOutlined className='blue-color' />
				</Button>
			</Tooltip>
		</Space>
	);
}
Example #6
Source File: WorkbenchPane.spec.tsx    From next-basics with GNU General Public License v3.0 5 votes vote down vote up
describe("WorkbenchPane", () => {
  beforeEach(() => {
    jest.spyOn(window, "requestAnimationFrame").mockImplementation((cb) => {
      Promise.resolve(1).then(cb);
      return 1;
    });
  });

  afterEach(() => {
    (window.requestAnimationFrame as jest.Mock).mockRestore();
  });

  it("should work", async () => {
    const onActiveChange = jest.fn();
    const onFirstActivated = jest.fn();
    const wrapper = mount(
      <WorkbenchPane
        titleLabel="Hello"
        active={false}
        onActiveChange={onActiveChange}
        onFirstActivated={onFirstActivated}
      />
    );
    expect(wrapper.find(RightOutlined).length).toBe(1);
    expect(onActiveChange).toBeCalledTimes(1);
    expect(onActiveChange).toHaveBeenNthCalledWith(1, false);
    expect(onFirstActivated).not.toBeCalled();

    wrapper.find(".pane-header").simulate("click");
    expect(wrapper.find(DownOutlined).length).toBe(1);
    expect(onActiveChange).toBeCalledTimes(2);
    expect(onActiveChange).toHaveBeenNthCalledWith(2, true);
    expect(onFirstActivated).toBeCalledTimes(1);

    wrapper.find(".pane-header").simulate("click");
    expect(wrapper.find(RightOutlined).length).toBe(1);
    expect(onActiveChange).toBeCalledTimes(3);
    expect(onActiveChange).toHaveBeenNthCalledWith(3, false);

    // Re-active will be ignored.
    wrapper.find(".pane-header").simulate("click");
    expect(onFirstActivated).toBeCalledTimes(1);

    expect(wrapper.find(".pane").hasClass("scrolled")).toBe(false);
    await act(async () => {
      wrapper.find(".pane-body").getDOMNode().scrollTop = 20;
      wrapper.find(".pane-body").simulate("scroll");
      await (global as any).flushPromises();
      wrapper.update();
    });
    expect(wrapper.find(".pane").hasClass("scrolled")).toBe(true);
  });
});
Example #7
Source File: WorkbenchPane.tsx    From next-basics with GNU General Public License v3.0 5 votes vote down vote up
export function WorkbenchPane({
  titleLabel,
  active,
  badge,
  onActiveChange,
  onFirstActivated,
}: WorkbenchPaneProps): React.ReactElement {
  const [internalActive, setInternalActive] = useState(active);
  const [activatedOnce, setActivatedOnce] = useState(false);

  useEffect(() => {
    setInternalActive(active);
  }, [active]);

  useEffect(() => {
    onActiveChange?.(internalActive);
  }, [internalActive, onActiveChange]);

  const handleClick = useCallback(() => {
    setInternalActive((previousActive) => !previousActive);
    if (!activatedOnce && !internalActive) {
      setActivatedOnce(true);
      onFirstActivated?.();
    }
  }, [activatedOnce, internalActive, onFirstActivated]);

  const scrollBodyRef = useRef<HTMLDivElement>();

  const [scrolled, setScrolled] = useState(false);

  const handleScroll = useMemo(
    () =>
      debounceByAnimationFrame((): void => {
        setScrolled(scrollBodyRef.current.scrollTop > 0);
      }),
    []
  );

  return (
    <div
      className={classNames("pane", {
        scrolled,
      })}
    >
      <div className="pane-header" tabIndex={0} onClick={handleClick}>
        <div className="pane-title">
          <span className="title-icon">
            {internalActive ? <DownOutlined /> : <RightOutlined />}
          </span>
          <div className="title-label">{titleLabel}</div>
        </div>
        <slot name="actions" />
        {badge !== null && <div className="badge">{badge}</div>}
        <div className="pane-scroll-shadow"></div>
      </div>
      <div
        className="pane-body custom-scrollbar-container"
        onScroll={handleScroll}
        ref={scrollBodyRef}
      >
        <slot name="content">
          <div
            style={{
              padding: "10px 20px",
              color: "var(--text-color-secondary)",
            }}
          >
            No content
          </div>
        </slot>
      </div>
    </div>
  );
}
Example #8
Source File: GeneralCarousel.tsx    From next-basics with GNU General Public License v3.0 5 votes vote down vote up
export function GeneralCarousel({
  speed,
  slidesToShow,
  slidesToScroll,
  autoplay,
  dots,
  components,
  carouselStyle,
  pauseOnDotsHover,
  adaptiveHeight,
  infinite,
  responsive,
  onHandleClick,
  noDataDesc,
  arrows,
  dotsTheme,
  useBrick,
  dataSource,
  autoplaySpeed,
}: GeneralCarouselProps): React.ReactElement {
  const comps = Array.isArray(components) ? components : compact([components]);
  const data = Array.isArray(dataSource) ? dataSource : compact([dataSource]);

  const carousel = (
    <Carousel
      className={classNames({
        "carousel-dots-dark": dotsTheme === "dark",
      })}
      style={carouselStyle}
      autoplay={autoplay}
      dots={dots}
      speed={speed}
      autoplaySpeed={autoplaySpeed}
      slidesToShow={slidesToShow}
      slidesToScroll={slidesToScroll}
      pauseOnDotsHover={pauseOnDotsHover}
      arrows={arrows}
      infinite={infinite}
      adaptiveHeight={adaptiveHeight}
      responsive={responsive}
      prevArrow={<LeftOutlined />}
      nextArrow={<RightOutlined />}
    >
      {useBrick
        ? renderCustomBrick(useBrick, data, onHandleClick)
        : renderCustomComp(comps, onHandleClick)}
    </Carousel>
  );

  return (
    <div className={style.generalCarousel}>
      {useBrick ? (
        data.length !== 0 ? (
          carousel
        ) : (
          <Empty description={noDataDesc} />
        )
      ) : comps.length !== 0 ? (
        carousel
      ) : (
        <Empty description={noDataDesc} />
      )}
    </div>
  );
}
Example #9
Source File: SecondaryList.tsx    From ant-simple-draw with MIT License 5 votes vote down vote up
SecondaryList: FC<SecondaryListType> = memo(({ data, fatherData }) => {
  const [oneModuleAll, setOneModuleAll] = useSetState<oneModuleAllType>({
    isShow: false,
    componentInfo: {},
  });

  return (
    <>
      <div
        className={styles.contentContainer}
        style={{
          display: !oneModuleAll.isShow ? 'block' : 'none',
        }}
      >
        {data.map((child, k) => (
          <React.Fragment key={k}>
            <>
              <div className={styles.head}>
                <h2 className={styles.title}>{child.title}</h2>
                <button
                  className={styles.more}
                  onClick={() => setOneModuleAll({ isShow: true, componentInfo: child })}
                >
                  <span>全部</span>
                  <RightOutlined />
                </button>
              </div>
              <Drag list={child.componentList} category={fatherData.category} />
            </>
          </React.Fragment>
        ))}
      </div>
      <div
        className={styles.contentContainer}
        style={{
          display: oneModuleAll.isShow ? 'block' : 'none',
        }}
      >
        <div className={styles.moreList}>
          <button className={styles.more} onClick={() => setOneModuleAll({ isShow: false })}>
            <LeftOutlined />
            <span>{oneModuleAll.componentInfo.title}</span>
          </button>
          <Drag list={oneModuleAll.componentInfo.componentList!} category={fatherData.category} />
        </div>
      </div>
    </>
  );
})
Example #10
Source File: index.tsx    From fe-v5 with Apache License 2.0 5 votes vote down vote up
LeftTree: React.FC<LeftTreeProps> = ({ clusterGroup = {}, busiGroup = {}, eventLevelGroup = {}, eventTypeGroup = {} }) => {
  const history = useHistory();
  const [collapse, setCollapse] = useState(localStorage.getItem('leftlist') === '1');
  const groupItems: IGroupItemProps[] = [
    clustersGroupContent(clusterGroup),
    busiGroupContent(busiGroup),
    {
      title: '事件级别',
      isShow: eventLevelGroup.isShow,
      render() {
        return (
          <SelectList
            dataSource={[
              { label: '一级告警', value: 1 },
              { label: '二级告警', value: 2 },
              { label: '三级告警', value: 3 },
            ]}
            defaultSelect={eventLevelGroup.defaultSelect}
            allowNotSelect={true}
            onChange={eventLevelGroup?.onChange}
          />
        );
      },
    },
    {
      title: '事件类别',
      isShow: eventTypeGroup.isShow,
      render() {
        return (
          <SelectList
            dataSource={[
              { label: 'Triggered', value: 0 },
              { label: 'Recovered', value: 1 },
            ]}
            defaultSelect={eventTypeGroup.defaultSelect}
            allowNotSelect={true}
            onChange={eventTypeGroup?.onChange}
          />
        );
      },
    },
  ];

  return (
    <div className={collapse ? 'left-area collapse' : 'left-area'}>
      <div
        className='collapse-btn'
        onClick={() => {
          localStorage.setItem('leftlist', !collapse ? '1' : '0');
          setCollapse(!collapse);
        }}
      >
        {!collapse ? <LeftOutlined /> : <RightOutlined />}
      </div>
      {/* 遍历渲染左侧栏内容 */}
      {groupItems.map(
        ({ title, isShow, shrink = false, render }: IGroupItemProps, i) =>
          isShow && (
            <div key={i} className={`left-area-group ${shrink ? 'group-shrink' : ''}`} style={typeof shrink === 'object' ? shrink.style : {}}>
              <div className='left-area-group-title'>
                {title}
                {title === '业务组' && <SettingOutlined onClick={() => history.push(`/busi-groups`)} />}
              </div>
              {render()}
            </div>
          ),
      )}
    </div>
  );
}
Example #11
Source File: detail.tsx    From dashboard with Apache License 2.0 4 votes vote down vote up
CustomerDetail: React.FC = () => {
  const queryFormRef = useRef<FormInstance>();
  const actionRef = useRef<ActionType>();
  const [customerDetail, setCustomerDetail] = useState<CustomerItem>()
  const [staffMap, setStaffMap] = useState<Dictionary<StaffOption>>({});
  const [allCustomerTagGroups, setAllCustomerTagGroups] = useState<CustomerTagGroupItem[]>([]);
  const [defaultCustomerTags, setDefaultCustomerTags] = useState<CustomerTag[]>([]);
  const [defaultInternalTagsIds, setDefaultInternalTagsIds] = useState<string []>([])
  const [personalTagModalVisible, setPersonalTagModalVisible] = useState(false)
  const [customerTagModalVisible, setCustomerTagModalVisible] = useState(false)
  const [internalTagList, setInternalTagList] = useState<InternalTags.Item[]>([])
  const [internalTagListMap, setInternalTagListMap] = useState<Dictionary<InternalTags.Item>>({});
  const [initialEvents, setInitialEvents] = useState<CustomerEvents.Item[]>([])
  const [currentTab, setCurrentTab] = useState('survey')
  const [basicInfoDisplay, setBasicInfoDisplay] = useState({} as any)// 展示哪些基本信息
  const [basicInfoValues, setBasicInfoValues] = useState({} as any) // 基本信息取值
  const [remarkValues, setRemarkValues] = useState<Remark[]>([])
  const [reloadCusDataTimesTamp, setReloadCusDataTimesTamp] = useState(Date.now)
  const [formRef] = Form.useForm()

  const params = new URLSearchParams(window.location.search);
  const currentStaff = localStorage.getItem('extStaffAdminID') as string
  const extCustomerID = params.get('ext_customer_id') || "";
  if (!extCustomerID) {
    message.error('传入参数请带上ID');
  }

  const extStaff = () => {
    const staffs: StaffItem[] = [];
    customerDetail?.staff_relations?.forEach((staff_relation) => {
      // @ts-ignore
      const staff = staffMap[staff_relation.ext_staff_id];
      if (staff) {
        staffs.push(staff);
      }
    });
    return staffs;
  }

  const getCustomerDetail = () => {
    const hide = message.loading("加载数据中");
    GetCustomerDetail(extCustomerID).then(res => {
      hide();
      if (res?.code !== 0) {
        message.error("获取客户详情失败");
        return;
      }
      setCustomerDetail(res?.data);
      const cusTags: any[] = [];
      const interTagsIds: any[] = []
      res?.data?.staff_relations?.forEach((relation: any) => {
        if (relation.ext_staff_id === currentStaff) {
          relation.customer_staff_tags?.forEach((tag: any) => {
            cusTags.push({...tag, name: tag.tag_name, ext_id: tag.ext_tag_id});
          });
          relation.internal_tags?.forEach((tagId: string) => {
            interTagsIds.push(tagId);
          })
        }

      });
      setDefaultCustomerTags(cusTags)
      setDefaultInternalTagsIds(interTagsIds)
    }).catch(() => {
      hide();
    })
  }

  const getInternalTags = () => {
    QueryInternalTags({page_size: 5000, ext_staff_id: currentStaff}).then(res => {
      if (res?.code === 0) {
        setInternalTagList(res?.data?.items)
        setInternalTagListMap(_.keyBy(res?.data?.items, 'id'))
      } else {
        message.error(res?.message)
      }
    })
  }

  const getCustomerRemark = () => { // 自定义信息id-key
    QueryCustomerRemark().then(res => {
      if (res?.code === 0) {
        console.log('QueryCustomerRemark', res.data)
      } else {
        message.error(res?.message)
      }
    })
  }

  const getBasicInfoDisplay = () => {
    GetCustomerBasicInfoDisplay().then(res => {
      if (res?.code === 0) {
        const displayData = res?.data
        delete displayData.id
        delete displayData.ext_corp_id
        delete displayData.created_at
        delete displayData.updated_at
        delete displayData.deleted_at
        setBasicInfoDisplay(displayData || {})
      } else {
        message.error(res?.message)
      }
    })
  }

  const getBasicInfoAndRemarkValues = () => {
    GetBasicInfoAndRemarkValues({
      ext_customer_id: extCustomerID,
      ext_staff_id: currentStaff,
    }).then(res => {
      if (res?.code === 0) {
        const resData = res?.data
        delete resData.id
        delete resData.ext_corp_id
        delete resData.ext_creator_id
        delete resData.ext_customer_id
        delete resData.ext_staff_id
        delete resData.created_at
        delete resData.updated_at
        delete resData.deleted_at
        delete resData.remark_values
        setBasicInfoValues(resData)
        setRemarkValues(res?.data?.remark_values || [])
      }
    })
  }

  const updateBasicInfoAndRemark = (basicInfoParams: any) => {
    UpdateBasicInfoAndRemark({
      ext_staff_id: currentStaff,
      ext_customer_id: extCustomerID,
      ...basicInfoParams
    }).then(res => {
      if (res?.code === 0) {
        message.success('客户信息更新成功')
        setReloadCusDataTimesTamp(Date.now)
      } else {
        message.error('客户信息更新失败')
      }
    })
  }

  useEffect(() => {
    getInternalTags()
    getCustomerDetail()
    getCustomerRemark()
    getBasicInfoDisplay()
    getBasicInfoAndRemarkValues()
  }, [reloadCusDataTimesTamp])

  useEffect(() => {
    QueryCustomerTagGroups({page_size: 5000}).then((res) => {
      if (res.code === 0) {
        setAllCustomerTagGroups(res?.data?.items);
      } else {
        message.error(res.message);
      }
    });
  }, []);

  useEffect(() => {
    QuerySimpleStaffs({page_size: 5000}).then((res) => {
      if (res.code === 0) {
        const staffs = res?.data?.items?.map((item: SimpleStaffInterface) => {
          return {
            label: item.name,
            value: item.ext_id,
            ...item,
          };
        }) || [];
        setStaffMap(_.keyBy<StaffOption>(staffs, 'ext_id'));
      } else {
        message.error(res.message);
      }
    });
  }, []);

  useEffect(() => {
    QueryCustomerEvents({
      ext_customer_id: extCustomerID,
      ext_staff_id: currentStaff,
      page_size: 5
    }).then(res => {
      console.log('QueryCustomerEventsQueryCustomerEvents', res)
      setInitialEvents(res?.data?.items || [])
    })
  }, [])

  useEffect(() => {
    formRef.setFieldsValue(basicInfoValues)
  }, [basicInfoValues])

  return (
    <PageContainer
      fixedHeader
      onBack={() => history.go(-1)}
      backIcon={<LeftOutlined/>}
      header={{
        title: '客户详情',
      }}
    >
      <ProCard>
        <Descriptions title="客户信息" column={1}>
          <Descriptions.Item>
            <div className={'customer-info-field'}>
              <div><img src={customerDetail?.avatar} alt={customerDetail?.name} style={{borderRadius: 5}}/></div>
              <div style={{fontSize: 16, marginLeft: 10}}>
                <p>{customerDetail?.name}</p>
                {customerDetail?.corp_name && (
                  <p style={{color: '#eda150', marginTop: 10}}>@{customerDetail?.corp_name}</p>
                )}
                {customerDetail?.type === 1 && (
                  <p style={{
                    color: '#5ec75d',
                    fontSize: '13px'
                  }}>@微信</p>
                )}
              </div>
            </div>
          </Descriptions.Item>
          <div>
            <div style={{width: 70, display: 'inline-block'}}>企业标签:</div>
            <div className={styles.tagContainer}>
              <Space direction={'horizontal'} wrap={true}>
                {
                  defaultCustomerTags?.length > 0 && defaultCustomerTags?.map((tag) =>
                    <Tag
                      key={tag?.id}
                      className={'tag-item selected-tag-item'}
                    >
                      {tag?.name}
                    </Tag>
                  )}
              </Space>
            </div>
            <Button
              key='addCusTags'
              icon={<EditOutlined/>}
              type={'link'}
              onClick={() => {
                setCustomerTagModalVisible(true);
              }}
            >
              编辑
            </Button>
          </div>

          <div>
            <div style={{width: 70, display: 'inline-block'}}>个人标签:</div>
            <div className={styles.tagContainer}>
              <Space direction={'horizontal'} wrap={true}>
                {
                  defaultInternalTagsIds?.length > 0 && defaultInternalTagsIds.map(id => internalTagListMap[id])?.map((tag) =>
                    <Tag
                      key={tag?.id}
                      className={'tag-item selected-tag-item'}
                    >
                      {tag?.name}
                      <span>
                     </span>
                    </Tag>
                  )}
              </Space>
            </div>
            <Button
              key='addInternalTags'
              icon={<EditOutlined/>}
              type={'link'}
              onClick={() => {
                setPersonalTagModalVisible(true);
              }}
            >
              编辑
            </Button>
          </div>
        </Descriptions>
      </ProCard>

      <ProCard
        tabs={{
          onChange: (activeKey: string) => setCurrentTab(activeKey),
          activeKey: currentTab
        }}
        style={{marginTop: 25}}
      >
        <ProCard.TabPane key="survey" tab="客户概况">
          <div className={styles.survey}>
            <div className={styles.cusSurveyLeft}>
              <div>
                <Descriptions title={<div><ContactsFilled/>&nbsp;&nbsp;添加客服信息</div>} layout="vertical" bordered
                              column={4}>
                  <Descriptions.Item label="所属员工">
                    <CollapsedStaffs limit={2} staffs={extStaff()}/>
                  </Descriptions.Item>

                  <Descriptions.Item label="客户来源">
                       <span>
                         {customerDetail?.staff_relations?.map((para) => {
                           return (`${addWayEnums[para.add_way || 0]}\n`);
                         })}
                       </span>
                  </Descriptions.Item>
                  <Descriptions.Item label="添加时间">
                    <Space>
                      {customerDetail?.staff_relations?.map((para) => {
                        return (
                          <div className={styles.staffTag}
                               dangerouslySetInnerHTML={{
                                 __html: moment(para.createtime)
                                   .format('YYYY-MM-DD HH:mm')
                                   .split(' ')
                                   .join('<br />'),
                               }}
                          />
                        );
                      })}
                    </Space>
                  </Descriptions.Item>
                  <Descriptions.Item label="更新时间">
                    <div
                      dangerouslySetInnerHTML={{
                        __html: moment(customerDetail?.updated_at)
                          .format('YYYY-MM-DD HH:mm')
                          .split(' ')
                          .join('<br />'),
                      }}
                    />
                  </Descriptions.Item>
                </Descriptions>
              </div>
              <Form form={formRef} onFinish={(values) => {
                console.log('ooooooooooooooovaluesvalues', values)
                const basicInfoParams = {...values}
                updateBasicInfoAndRemark(basicInfoParams)
              }}>
                <div style={{paddingTop: 20}} className={styles.baseInfoContainer}>
                  <Descriptions
                    title={<div><BookFilled/>&nbsp;&nbsp;基本信息</div>}
                    bordered
                    column={2}
                    size={'small'}
                  >
                    {
                      Object.keys(basicInfoDisplay).map(key => {
                        return <Descriptions.Item label={basicInfo[key]}>
                          <TableInput name={key} />
                        </Descriptions.Item>
                      })
                    }
                  </Descriptions>
                </div>
                {
                  remarkValues.length > 0 && <div style={{paddingTop: 20}} className={styles.customInfoContainer}>
                    <Descriptions
                      title={<div><EditFilled/>&nbsp;&nbsp;自定义信息</div>}
                      bordered
                      column={2}
                      size={'small'}
                    >
                      <Descriptions.Item label="sfdsf">
                        <TableInput name={'aa'}/>
                      </Descriptions.Item>
                      <Descriptions.Item label="违法的">
                        <TableInput name={'bb'}/>
                      </Descriptions.Item>
                      <Descriptions.Item label="sdf434">
                        <TableInput name={'cc'}/>
                      </Descriptions.Item>
                      <Descriptions.Item label="yjkyujy">
                        <TableInput name={'dd'}/>
                      </Descriptions.Item>
                    </Descriptions>
                  </div>
                }
                <div style={{display: 'flex', justifyContent: 'center', marginTop: 40}}>
                  <Space>
                    <Button onClick={() => formRef.setFieldsValue(basicInfoValues)}>重置</Button>
                    <Button type={"primary"} onClick={() => {
                      formRef.submit()
                    }}>提交</Button>
                  </Space>
                </div>
              </Form>
            </div>

            <div className={styles.cusSurveyRight}>
              <div className={styles.eventsTitle}>
                <span className={styles.titleText}><SoundFilled/>&nbsp;&nbsp;客户动态</span>
                <a onClick={() => setCurrentTab('events')} style={{fontSize: 12}}>查看更多<RightOutlined/></a>
              </div>
              <Events data={initialEvents.filter(elem => elem !== null)} simpleRender={true} staffMap={staffMap}
                      extCustomerID={extCustomerID}/>
            </div>
          </div>

        </ProCard.TabPane>

        <ProCard.TabPane key="events" tab="客户动态">
          <Events staffMap={staffMap} extCustomerID={extCustomerID}/>
        </ProCard.TabPane>

        <ProCard.TabPane key="room" tab="所在群聊">
          <ProTable<GroupChatItem>
            search={false}
            formRef={queryFormRef}
            actionRef={actionRef}
            className={'table'}
            scroll={{x: 'max-content'}}
            columns={columns}
            rowKey="id"
            toolBarRender={false}
            bordered={false}
            tableAlertRender={false}
            dateFormatter="string"
            request={async (originParams: any, sort, filter) => {
              return ProTableRequestAdapter(
                originParams,
                sort,
                filter,
                QueryCustomerGroupsList,
              );
            }}
          />
        </ProCard.TabPane>

        <ProCard.TabPane key="chat" tab="聊天记录">
          {
            setStaffMap[currentStaff]?.enable_msg_arch === 1 ? <Button
                key={'chatSession'}
                type={"link"}
                icon={<ClockCircleOutlined style={{fontSize: 16, verticalAlign: '-3px'}}/>}
                onClick={() => {
                  window.open(`/staff-admin/corp-risk-control/chat-session?staff=${currentStaff}`)
                }}
              >
                聊天记录查询
              </Button>
              :
              <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={<span>员工暂未开启消息存档</span>}/>
          }
        </ProCard.TabPane>

      </ProCard>

      <CustomerTagSelectionModal
        type={'customerDetailEnterpriseTag'}
        isEditable={true}
        withLogicalCondition={false}
        width={'630px'}
        visible={customerTagModalVisible}
        setVisible={setCustomerTagModalVisible}
        defaultCheckedTags={defaultCustomerTags}
        onFinish={(selectedTags) => {
          const removeAry = _.difference(defaultCustomerTags.map(dt => dt.ext_id), selectedTags.map(st => st.ext_id))
          UpdateCustomerTags({
            // @ts-ignore
            add_ext_tag_ids: selectedTags.map((tag) => tag.ext_id),
            ext_customer_ids: [extCustomerID],
            ext_staff_id: currentStaff,
            // @ts-ignore
            remove_ext_tag_ids: removeAry
          }).then(() => {
            getCustomerDetail()
          })
        }}
        allTagGroups={allCustomerTagGroups}
      />

      <InternalTagModal
        width={560}
        allTags={internalTagList}
        allTagsMap={internalTagListMap}
        setAllTags={setInternalTagList}
        visible={personalTagModalVisible}
        setVisible={setPersonalTagModalVisible}
        defaultCheckedTagsIds={defaultInternalTagsIds}
        reloadTags={getInternalTags}
        onFinish={(selectedTags) => {
          console.log('selectedTags', selectedTags)
          const removeAry = _.difference(defaultInternalTagsIds, selectedTags.map(st => st.id))
          CustomerInternalTags({
            // @ts-ignore
            add_ext_tag_ids: selectedTags.map((tag) => tag.id),
            ext_customer_id: extCustomerID,
            ext_staff_id: currentStaff,
            // @ts-ignore
            remove_ext_tag_ids: removeAry
          }).then(() => {
            getCustomerDetail()
          })
        }
        }
      />
    </PageContainer>
  );
}
Example #12
Source File: App.tsx    From pcap2socks-gui with MIT License 4 votes vote down vote up
render() {
    return (
      <Layout className="layout">
        <Content className="content-wrapper">
          <div className="content">
            {(() => {
              switch (this.state.stage) {
                case STAGE_WELCOME:
                  return this.renderWelcome();
                case STAGE_INTERFACE:
                  return this.renderInterface();
                case STAGE_DEVICE:
                  return this.renderDevice();
                case STAGE_PROXY:
                  return this.renderProxy();
                case STAGE_RUNNING:
                  return this.renderRunning();
                default:
                  return;
              }
            })()}
          </div>
          <div className="footer">
            {(() => {
              if (this.state.stage > STAGE_WELCOME && this.state.stage <= STAGE_PROXY) {
                return (
                  <Button
                    className="button"
                    disabled={this.state.loading > 0}
                    icon={<LeftOutlined />}
                    onClick={() => this.setState({ stage: this.state.stage - 1 })}
                  >
                    上一步
                  </Button>
                );
              }
            })()}
            {(() => {
              if (this.state.stage === STAGE_INTERFACE) {
                return (
                  <Button
                    className="button"
                    disabled={this.state.loading > 0 && this.state.loading !== 1}
                    icon={<ReloadOutlined />}
                    onClick={this.updateInterfaces}
                  >
                    刷新网卡列表
                  </Button>
                );
              }
            })()}
            {(() => {
              if (this.state.stage === STAGE_PROXY) {
                return (
                  <Button
                    className="button"
                    disabled={this.state.loading > 0}
                    icon={<FolderOpenOutlined />}
                    onClick={() => {
                      const node = document.getElementById("open");
                      if (node) {
                        node.click();
                      }
                    }}
                  >
                    导入代理配置
                    <input id="open" type="file" onChange={this.import} style={{ display: "none" }} />
                  </Button>
                );
              }
            })()}
            {(() => {
              if (this.state.stage === STAGE_PROXY) {
                return (
                  <Button className="button" icon={<ExportOutlined />} onClick={this.export}>
                    导出代理配置
                  </Button>
                );
              }
            })()}
            {(() => {
              if (this.state.stage === STAGE_PROXY) {
                return (
                  <Button
                    className="button"
                    disabled={this.state.loading > 0 && this.state.loading !== 2}
                    loading={this.state.loading === 2}
                    icon={<ExperimentOutlined />}
                    onClick={this.test}
                  >
                    测试代理服务器
                  </Button>
                );
              }
            })()}
            {(() => {
              if (this.state.stage === STAGE_WELCOME && this.state.ready) {
                return (
                  <Tooltip title={this.state.destination}>
                    <Button
                      className="button"
                      type="primary"
                      disabled={this.state.loading > 0 && this.state.loading !== 3}
                      loading={this.state.loading === 3}
                      icon={<PlayCircleOutlined />}
                      onClick={this.run}
                    >
                      以上次的配置运行
                    </Button>
                  </Tooltip>
                );
              }
            })()}
            {(() => {
              if (this.state.stage >= STAGE_WELCOME && this.state.stage < STAGE_PROXY) {
                return (
                  <Button
                    className="button"
                    disabled={this.state.loading > 0}
                    icon={<RightOutlined />}
                    type="primary"
                    onClick={() => this.setState({ stage: this.state.stage + 1 })}
                  >
                    下一步
                  </Button>
                );
              }
            })()}
            {(() => {
              if (this.state.stage === STAGE_PROXY) {
                return (
                  <Button
                    className="button"
                    type="primary"
                    disabled={this.state.loading > 0 && this.state.loading !== 3}
                    loading={this.state.loading === 3}
                    icon={<PoweroffOutlined />}
                    onClick={this.run}
                  >
                    运行
                  </Button>
                );
              }
            })()}
            {(() => {
              if (this.state.stage === STAGE_RUNNING) {
                return (
                  <Button className="button" icon={<GlobalOutlined />} onClick={this.notifyNetwork}>
                    显示网络设置
                  </Button>
                );
              }
            })()}
            {(() => {
              if (this.state.stage === STAGE_RUNNING) {
                return (
                  <Button
                    className="button"
                    type="primary"
                    danger
                    disabled={this.state.loading > 0 && this.state.loading !== 4}
                    loading={this.state.loading === 4}
                    icon={<PoweroffOutlined />}
                    onClick={this.stopConfirm}
                  >
                    停止
                  </Button>
                );
              }
            })()}
          </div>
        </Content>
      </Layout>
    );
  }
Example #13
Source File: HorizontalScrollable.tsx    From oxen-website with GNU General Public License v3.0 4 votes vote down vote up
function HorizontalScrollableInner(props: Props) {
  const { onItemClick, children } = props;

  const scrollRef = useRef(null);
  const innerContentRef = useRef(null);

  const { x } = useScroll(scrollRef);
  const pageWidth = useWindowSize().width;
  const scrollDistance = pageWidth > 1400 ? 450 : pageWidth / 3;

  const [rightScrollHidden, setRightScrollHidden] = useState(false);

  const { isDesktop } = useContext(ScreenContext);

  const handleLeftScroll = () => {
    scrollRef.current.scrollBy({
      left: -scrollDistance,
      behavior: 'smooth',
    });
  };

  const handleRightScroll = () => {
    scrollRef.current.scrollBy({
      left: scrollDistance,
      behavior: 'smooth',
    });
  };

  function handleItemClick() {
    if (onItemClick) {
      onItemClick();
    }
  }

  useEffect(() => {
    const isFullRight =
      scrollRef.current.scrollWidth - scrollRef.current.clientWidth ===
      scrollRef.current.scrollLeft;

    const tooSmallToScroll =
      innerContentRef.current.clientWidth < scrollRef.current.clientWidth;

    setRightScrollHidden(tooSmallToScroll || isFullRight);
  }, [x, children]);

  return (
    <div className="relative flex w-full">
      <div
        className={classNames(
          'absolute left-0 flex items-center justify-between h-full w-full',
          !isDesktop && 'hidden',
        )}
      >
        <div
          className={classNames(
            'flex flex-col justify-center h-full z-50 duration-300 -ml-8',
            x <= 1 && 'opacity-0',
          )}
        >
          <LeftOutlined
            onClick={handleLeftScroll}
            className={classNames('h-20 mt-1 cursor-pointer')}
          />
        </div>

        <div
          className={classNames(
            'flex flex-col justify-center h-full z-50 duration-300 -mr-8',
            rightScrollHidden && 'opacity-0',
          )}
        >
          <RightOutlined
            onClick={handleRightScroll}
            className="h-20 mt-1 cursor-pointer"
          />
        </div>
      </div>
      <div
        ref={scrollRef}
        className={classNames(
          'relative',
          'w-full',
          'hide_scroll',
          'scrolling-touch hide-scroll',
          isDesktop ? 'overflow-x-scroll' : 'overflow-x-scroll',
        )}
      >
        <div
          ref={innerContentRef}
          className={classNames('flex space-x-8 overflow-y-visible')}
          style={{
            width: 'min-content',
            marginLeft: `${!isDesktop ? UI.PAGE_CONTAINED_PADDING_VW : 0}vw`,
            paddingRight: `${!isDesktop ? UI.PAGE_CONTAINED_PADDING_VW : 0}vw`,
          }}
        >
          {children}
        </div>
      </div>
    </div>
  );
}
Example #14
Source File: PopularTimes.tsx    From office-hours with GNU General Public License v3.0 4 votes vote down vote up
export default function PopularTimes({ heatmap }: HeatmapProps): ReactElement {
  const [currentDayOfWeek, setCurrentDayOfWeek] = useState(new Date().getDay());
  const [firstHour, lastHour] = findWeekMinAndMax(heatmap);
  const dailyAvgWaitTimes: number[] = chunk(heatmap, 24).map((hours) => {
    const filteredOfficeHours = hours.filter((v) => v !== -1);
    return filteredOfficeHours.length > 0 ? mean(filteredOfficeHours) : -1;
  });

  return (
    <div className="hide-in-percy">
      <TitleRow>
        <h2>Wait Times on</h2>
        <Dropdown
          trigger={["click"]}
          overlay={
            <Menu>
              {DAYS_OF_WEEK.map((dayName, i) => (
                <Menu.Item key={dayName}>
                  <a onClick={() => setCurrentDayOfWeek(i)}>{dayName}</a>
                </Menu.Item>
              ))}
            </Menu>
          }
        >
          <WeekdayDropdown>
            {DAYS_OF_WEEK[currentDayOfWeek]}
            <DownOutlined />
          </WeekdayDropdown>
        </Dropdown>
      </TitleRow>
      <GraphWithArrow>
        <GraphArrowButtons
          onClick={() => setCurrentDayOfWeek((7 + currentDayOfWeek - 1) % 7)}
        >
          <LeftOutlined />
        </GraphArrowButtons>
        <GraphContainer>
          <ParentSize>
            {({ width }) => (
              <TimeGraph
                values={heatmap
                  .slice(currentDayOfWeek * 24, (currentDayOfWeek + 1) * 24 - 1)
                  .map((i) => (i < 0 ? 0 : Math.floor(i)))}
                maxTime={Math.max(...heatmap)}
                firstHour={firstHour}
                lastHour={lastHour}
                width={width}
                height={220}
              />
            )}
          </ParentSize>
        </GraphContainer>
        <GraphArrowButtons
          onClick={() => setCurrentDayOfWeek((currentDayOfWeek + 1) % 7)}
        >
          <RightOutlined />
        </GraphArrowButtons>
      </GraphWithArrow>
      {dailyAvgWaitTimes[currentDayOfWeek] >= 0 && (
        <GraphNotes>
          <ClockCircleOutlined /> {DAYS_OF_WEEK[currentDayOfWeek]}s have{" "}
          <strong>
            {generateBusyText(currentDayOfWeek, dailyAvgWaitTimes)}
          </strong>{" "}
          wait times.
        </GraphNotes>
      )}
      {new Date().getDay() === currentDayOfWeek &&
        heatmap[currentDayOfWeek * 24 + new Date().getHours()] >= 0 && (
          <GraphNotes>
            <HourglassOutlined /> At {formatDateHour(new Date().getHours())},
            people generally wait{" "}
            <strong>
              {formatWaitTime(
                heatmap[currentDayOfWeek * 24 + new Date().getHours()]
              )}
            </strong>
            .
          </GraphNotes>
        )}
    </div>
  );
}
Example #15
Source File: index.tsx    From amiya with MIT License 4 votes vote down vote up
export default function Demo() {
  // 列表控制
  const listRef = useRef<any>()
  // 选中的年份
  const [activeYear, setActiveYear] = useState(1)
  // 选中的状态
  const [activeStatus, setActiveStatus] = useState(1)
  // 选中的 tab
  const [activeTab, setActiveTab] = useState(1)
  // 选中的标签
  const [activeTag, setActiveTag] = useState(undefined)
  // 标签是否可见
  const [tagVisible, setTagVisible] = useState(false)
  // 查询参数 打印用
  const [searchValue, setSearchValue] = useState({})
  // 查询区域是否可见
  const searchVisible = useMemo(() => {
    return activeTab !== 6
  }, [activeTab])
  // 查询参数
  const searchParams = useMemo(() => {
    return {
      activeYear,
      activeStatus,
      activeTab,
      activeTag
    }
  }, [activeTab, activeYear, activeStatus, activeTag])

  useEffect(() => {
    // 查询参数改变,刷新列表
    listRef.current.reset()
  }, [searchParams])

  useEffect(() => {
    // 如果不显示标签,则把标签值值设置为空
    if (!tagVisible) {
      setActiveTag(undefined)
    }
  }, [tagVisible])

  useEffect(() => {
    // 查询区域不显示时,清空它的值
    if (!searchVisible) {
      setActiveTag(undefined)
      setTagVisible(false)
    }
  }, [searchVisible])

  return (
    <div className="space-list">
      <AySearchList
        ref={listRef}
        title={
          <Space size={24}>
            <Tabs activeKey={activeTab + ''} onChange={key => setActiveTab(Number(key))}>
              {tabOptions.map(option => (
                <Tabs.TabPane key={option.value} className="goods" tab={option.label} />
              ))}
            </Tabs>
          </Space>
        }
        api={(searchValues: AnyKeyProps) =>
          new Promise(resolve => {
            setSearchValue(searchValues)
            setTimeout(() => {
              resolve({
                content: data,
                totalCount: data.length
              })
            }, 500)
          })
        }
        extendSearchParams={searchParams}
        extraVisible={false}
        autoload={false}
        listExtend={{
          itemLayout: 'vertical'
        }}
        onParamsChange={(values: AnyKeyProps) => {
          // 翻页 & 查询时,滚动到最顶部
          window.scrollTo({ behavior: 'smooth', top: 0 })
        }}
        listHeader={
          <div>
            {tagVisible && (
              <div>
                <div className="space-list-search-tags">
                  <label>订单类型:</label>
                  <AyTagGroup value={activeTag} onChange={setActiveTag} options={tagOptions} />
                </div>
                <div className="space-list-search-row">
                  <Button onClick={() => setTagVisible(false)}>返回</Button>
                </div>
              </div>
            )}
            {!searchVisible && (
              <div className="space-list-search-row">
                <Alert
                  showIcon
                  message="说明"
                  description={
                    <ul>
                      <li>1. 只有已取消和已完成的订单可以删除;</li>
                      <li>
                        2.
                        被删除的订单将无法进行评价、晒单和申请售后等操作;如果想继续这些操作,可以先将被删除的订单还原;
                      </li>
                      <li>3. 订单被永久删除后无法还原。</li>
                    </ul>
                  }
                />
              </div>
            )}
            <Row className="space-list-header">
              <Col flex="150px">
                <Select
                  style={{ minWidth: 200 }}
                  options={yearOptions}
                  bordered={false}
                  value={activeYear}
                  onChange={setActiveYear}
                />
              </Col>
              <Col flex="1" style={{ paddingLeft: 8 }}>
                订单详情
              </Col>
              <Col span={3} className="space-list-center">
                收货人
              </Col>
              <Col span={3} className="space-list-center">
                金额
              </Col>
              <Col span={3} className="space-list-center">
                <Select
                  style={{ width: '100%' }}
                  options={statusOptions}
                  bordered={false}
                  value={activeStatus}
                  onChange={setActiveStatus}
                />
              </Col>
              <Col span={3} className="space-list-center">
                操作
              </Col>
            </Row>
          </div>
        }
        renderItem={(record: Record) => {
          // 卡片渲染内容
          return (
            <List.Item key={record.id}>
              <div className="space-list-card">
                {record.splitInfo && (
                  <>
                    <div className="space-list-card-header light">
                      <Space size="large">
                        <Text>2020-05-06 23:59:59</Text>
                        <span>
                          <Text type="secondary">订单号:</Text>78074445382
                        </span>
                      </Space>
                      <Text type="secondary">
                        您订单中的商品在不同库房或属不同商家,故拆分为以下订单分开配送,给您带来的不便敬请谅解。
                      </Text>
                    </div>
                    <div className="space-list-card-header gray">
                      <Space size="large">
                        <span>
                          <Text type="secondary">收货人:</Text>123
                        </span>
                        <span>
                          <Text type="secondary">订单金额:</Text>¥123
                        </span>
                        <span>
                          <Text type="secondary">支付方式:</Text>在线支付
                        </span>
                        <span>
                          <Text type="secondary">订单状态:</Text>已拆分
                        </span>
                      </Space>
                      <AyButton sub>
                        查看拆分详情
                        <RightOutlined />
                      </AyButton>
                    </div>
                  </>
                )}
                {record.groups?.map((group: Record, index: number) => (
                  <div className="space-list-card-group">
                    <div
                      className={classNames('space-list-card-header', !record.splitInfo && index === 0 ? 'light' : '')}
                    >
                      <Space size="large">
                        <Text>2020-05-06 23:59:59</Text>
                        <span>
                          <Text type="secondary">订单号:</Text> 78074445382
                        </span>
                        <a>
                          <Space size="small">
                            <SmileOutlined />
                            卖橘子的商家
                          </Space>
                        </a>
                      </Space>
                      <AyButton
                        className="space-list-card-delete"
                        confirm
                        confirmMsg="确定要删除吗?删除后,您可以在订单回收站还原该订单,也可以做永久删除。"
                        size="small"
                        icon={<DeleteOutlined />}
                      />
                    </div>
                    <Row className="space-list-card-info">
                      <Col span={12} className="space-list-card-left">
                        {group.goods?.map((goods: Record) => (
                          <Row key={goods.id} wrap={false} gutter={8} className="space-list-card-goods">
                            <Col flex="90px">
                              <Image src={orange} width={80} height={80} />
                            </Col>
                            <Col flex="1">
                              <Paragraph ellipsis={{ rows: 2, tooltip: '好吃的橘子', symbol: '...' }}>
                                {record.message || '商品名称'}
                              </Paragraph>
                              <a>
                                <Space size="small">
                                  <AppstoreAddOutlined />
                                  找搭配
                                </Space>
                              </a>
                            </Col>
                            <Col flex="50px" className="space-list-center">
                              x1
                            </Col>
                            <Col flex="100px" className="space-list-center">
                              <a>申请售后</a>
                            </Col>
                          </Row>
                        ))}
                      </Col>
                      <Col span={3} className="space-list-center space-list-cell">
                        <Space>
                          <Avatar src="购买者头像" />
                          购买者
                        </Space>
                      </Col>
                      <Col span={3} className="space-list-center space-list-cell">
                        <div>¥25.55</div>
                        <div>
                          <Text type="secondary">在线支付</Text>
                        </div>
                      </Col>
                      <Col span={3} className="space-list-center space-list-cell">
                        <div>已完成</div>
                        <div>
                          <a>订单详情</a>
                        </div>
                      </Col>
                      <Col span={3} className="space-list-center space-list-cell">
                        <div>
                          <a>查看发票</a>
                        </div>
                        <div>
                          <a>立即购买</a>
                        </div>
                      </Col>
                    </Row>
                  </div>
                ))}
                {record.steps?.map((step: Record, index: number) => (
                  <Row className="space-list-card-footer gray" key={step.id}>
                    <Col span={15}>
                      阶段{index + 1}:{step.label}
                    </Col>
                    <Col span={3} className="space-list-center">
                      ¥50
                    </Col>
                    <Col span={3} className="space-list-center">
                      已完成
                    </Col>
                    <Col span={3} className="space-list-center">
                      2
                    </Col>
                  </Row>
                ))}
              </div>
            </List.Item>
          )
        }}
      >
        <AyFields>
          <AyField key="__search" type="input-group" search={{ position: 'more', hidden: !searchVisible }}>
            <AyField
              key="keyword"
              type="search"
              placeholder="商品名称/商品编号/订单号"
              enterButton={false}
              style={{ width: 300 }}
            />
            <AyField
              key="__btn"
              type="custom"
              renderContent={() => (
                <Button className="space-list-toggle" onClick={() => setTagVisible(!tagVisible)}>
                  高级
                  {tagVisible ? <UpOutlined /> : <DownOutlined />}
                </Button>
              )}
            />
          </AyField>
        </AyFields>
      </AySearchList>
      {/* 页面打印用,实际使用删掉即可 */}
      <Divider orientation="left">查询参数</Divider>
      {Object.keys(searchValue).length && <pre>{JSON.stringify(searchValue, null, 2)}</pre>}
    </div>
  )
}
Example #16
Source File: HTTPFlowDetail.tsx    From yakit with GNU Affero General Public License v3.0 4 votes vote down vote up
HTTPFlowDetail: React.FC<HTTPFlowDetailProp> = (props) => {
    const [flow, setFlow] = useState<HTTPFlow>();
    const [loading, setLoading] = useState(false);

    const actionFuzzer = [
        {
            id: 'send-fuzzer-info',
            label: '发送到Fuzzer',
            contextMenuGroupId: 'send-fuzzer-info',
            run: () => {
                ipcRenderer.invoke("send-to-tab", {
                    type: "fuzzer",
                    data: {
                        isHttps: flow?.IsHTTPS,
                        request: Buffer.from(flow?.Request || []).toString("utf8")
                    }
                })
                if (props.onClose) props.onClose()
            }
        },
        {
            id: 'send-to-plugin',
            label: '发送到数据包扫描',
            contextMenuGroupId: 'send-fuzzer-info',
            run: () => ipcRenderer.invoke("send-to-packet-hack", {
                request: flow?.Request,
                ishttps: flow?.IsHTTPS,
                response: flow?.Response
            })

        }
    ]

    useEffect(() => {
        if (!props.hash) {
            return
        }
        //
        // ipcRenderer.on(props.hash, (e: any, data: HTTPFlow) => {
        //     setFlow(data)
        //     setTimeout(() => setLoading(false), 300)
        // })
        // ipcRenderer.on(`ERROR:${props.hash}`, (e: any, details: any) => {
        //     failed(`查询该请求失败[${props.hash}]: ` + details)
        // })

        setLoading(true)
        ipcRenderer.invoke("GetHTTPFlowByHash", {Hash: props.hash}).then((data: HTTPFlow) => {
            setFlow(data)
        }).catch(e => {
            failed(`GetHTTPFlow ByHash[${props.hash}] failed`)
        }).finally(() => setTimeout(() => setLoading(false), 300))
        // ipcRenderer.invoke("get-http-flow", props.hash)

        return () => {
            // ipcRenderer.removeAllListeners(props.hash)
            // ipcRenderer.removeAllListeners(`ERROR:${props.hash}`)
        }
    }, [props.hash])

    return <Spin spinning={loading} style={{width: "100%", marginBottom: 24}}>
        {flow ? <>
            {props.noHeader ? undefined : <PageHeader
                title={`请求详情`} subTitle={props.hash}
                extra={
                    props.fetchRequest ?
                        <Space>
                            <Tooltip title={"上一个请求"}>
                                <Button type="link" disabled={!!props.isFront}
                                        icon={<LeftOutlined/>} onClick={() => {
                                    props?.fetchRequest!(1)
                                }}></Button>
                            </Tooltip>
                            <Tooltip title={"下一个请求"}>
                                <Button type="link" disabled={!!props.isBehind}
                                        icon={<RightOutlined/>} onClick={() => {
                                    props?.fetchRequest!(2)
                                }}></Button>
                            </Tooltip>
                        </Space>
                        :
                        <></>
                }/>
            }
            <Space direction={"vertical"} style={{width: "100%"}}>
                <Descriptions column={4} bordered={true} size={"small"}>
                    <Descriptions.Item key={"method"} span={1} label={"HTTP 方法"}><Tag color={"geekblue"}><Text
                        style={{maxWidth: 500}}>{flow.Method}</Text></Tag></Descriptions.Item>
                    <Descriptions.Item key={"url"} span={3} label={"请求 URL"}>
                        <Text style={{maxWidth: 500}} copyable={true}>{flow.Url}</Text>
                    </Descriptions.Item>
                    <Descriptions.Item key={"https"} span={1} label={"HTTPS"}><Tag color={"geekblue"}>
                        <div
                            style={{maxWidth: 500}}>{flow.IsHTTPS ? "True" : "False"}</div>
                    </Tag></Descriptions.Item>
                    <Descriptions.Item key={"status"} span={1} label={"StatusCode"}><Tag
                        color={"geekblue"}>{flow.StatusCode}</Tag></Descriptions.Item>
                    <Descriptions.Item key={"size"} span={1} label={"Body大小"}><Tag color={"geekblue"}>
                        <div style={{maxWidth: 500}}>{flow.BodySizeVerbose}</div>
                    </Tag></Descriptions.Item>
                    <Descriptions.Item key={"type"} span={1} label={"Content-Type"}><Tag color={"geekblue"}>
                        <div style={{maxWidth: 500}}>{flow.ContentType}</div>
                    </Tag></Descriptions.Item>
                </Descriptions>
                <div style={{width: "100%", overflow: "auto"}}>
                    {flow.GetParams.length > 0 || flow.PostParams.length > 0 || flow.CookieParams.length > 0 ? <Tabs>
                        {flow.GetParams.length > 0 && <Tabs.TabPane key={"get"} tab={"GET 参数"}>
                            <FuzzableParamList data={flow.GetParams} sendToWebFuzzer={() => {
                                if (props.onClose) props.onClose()
                            }}/>
                        </Tabs.TabPane>}
                        {flow.PostParams.length > 0 && <Tabs.TabPane key={"post"} tab={"POST 参数"}>
                            <FuzzableParamList data={flow.PostParams} sendToWebFuzzer={() => {
                                if (props.onClose) props.onClose()
                            }}/>
                        </Tabs.TabPane>}
                        {flow.CookieParams.length > 0 && <Tabs.TabPane key={"cookie"} tab={"Cookie 参数"}>
                            <FuzzableParamList data={flow.CookieParams} sendToWebFuzzer={() => {
                                if (props.onClose) props.onClose()
                            }}/>
                        </Tabs.TabPane>}
                    </Tabs> : ""}
                </div>

                <Row gutter={8}>
                    <Col span={12}>
                        <Card title={"原始 HTTP 请求"} size={"small"} bodyStyle={{padding: 0}}>
                            <div style={{height: 350}}>
                                <YakEditor readOnly={true} type={"http"}//theme={"fuzz-http-theme"}
                                           value={new Buffer(flow.Request).toString("utf-8")}
                                           actions={[...actionFuzzer]}/>
                            </div>
                        </Card>
                    </Col>
                    <Col span={12}>
                        <Card title={"原始 HTTP 响应"} size={"small"} bodyStyle={{padding: 0}}>
                            <div style={{height: 350}}>
                                <YakEditor readOnly={true} type={"http"}// theme={"fuzz-http-theme"}
                                           value={new Buffer(flow.Response).toString("utf-8")}
                                />
                            </div>
                        </Card>
                    </Col>
                </Row>

                {/*<Collapse>*/}
                {/*    <Collapse.Panel key={"request-raw"} header={"原始 HTTP 请求数据包内容"}>*/}

                {/*    </Collapse.Panel>*/}
                {/*    <Collapse.Panel key={"response-raw"} header={"原始 HTTP 响应数据包内容"}>*/}

                {/*    </Collapse.Panel>*/}
                {/*</Collapse>*/}
                <Row gutter={8}>
                    <Col span={12}>
                        <Collapse defaultActiveKey={"request"}>
                            <Collapse.Panel key={"request"} header={"Request Headers"}>
                                <Descriptions bordered={true} column={1} size={"small"}>
                                    {(flow?.RequestHeader || []).sort((i, e) => {
                                        return i.Header.localeCompare(e.Header)
                                    }).map(i => {
                                        return <Descriptions.Item key={i.Header} label={<Text style={{width: 240}}>
                                            <Tag>{i.Header}</Tag>
                                        </Text>}>
                                            <Text
                                                copyable={true}
                                                style={{maxWidth: 500}}
                                                ellipsis={{tooltip: true}}>{i.Value}</Text>
                                        </Descriptions.Item>
                                    })}
                                </Descriptions>
                            </Collapse.Panel>
                        </Collapse>
                    </Col>
                    <Col span={12}>
                        <Collapse defaultActiveKey={"response"}>
                            <Collapse.Panel key={"response"} header={"Response Headers"}>
                                <Descriptions bordered={true} column={1} size={"small"}>
                                    {(flow?.ResponseHeader || []).sort((i, e) => {
                                        return i.Header.localeCompare(e.Header)
                                    }).map(i => {
                                        return <Descriptions.Item key={i.Header} label={<Text style={{width: 240}}>
                                            <Tag>{i.Header}</Tag>
                                        </Text>}>
                                            <Text
                                                copyable={true}
                                                style={{maxWidth: 500}}
                                                ellipsis={{tooltip: true}}>{i.Value}</Text>
                                        </Descriptions.Item>
                                    })}
                                </Descriptions>
                            </Collapse.Panel>
                        </Collapse>
                    </Col>
                </Row>
            </Space>
        </> : ""}
    </Spin>
}
Example #17
Source File: HTTPFuzzerPage.tsx    From yakit with GNU Affero General Public License v3.0 4 votes vote down vote up
HTTPFuzzerPage: React.FC<HTTPFuzzerPageProp> = (props) => {
    // params
    const [isHttps, setIsHttps, getIsHttps] = useGetState<boolean>(props.fuzzerParams?.isHttps || props.isHttps || false)
    const [noFixContentLength, setNoFixContentLength] = useState(false)
    const [request, setRequest, getRequest] = useGetState(props.fuzzerParams?.request || props.request || defaultPostTemplate)
    const [concurrent, setConcurrent] = useState(props.fuzzerParams?.concurrent || 20)
    const [forceFuzz, setForceFuzz] = useState<boolean>(props.fuzzerParams?.forceFuzz || true)
    const [timeout, setParamTimeout] = useState(props.fuzzerParams?.timeout || 10.0)
    const [proxy, setProxy] = useState(props.fuzzerParams?.proxy || "")
    const [actualHost, setActualHost] = useState(props.fuzzerParams?.actualHost || "")
    const [advancedConfig, setAdvancedConfig] = useState(false)
    const [redirectedResponse, setRedirectedResponse] = useState<FuzzerResponse>()
    const [historyTask, setHistoryTask] = useState<HistoryHTTPFuzzerTask>();
    const [hotPatchCode, setHotPatchCode] = useState<string>("");

    // filter
    const [_, setFilter, getFilter] = useGetState<FuzzResponseFilter>({
        Keywords: [],
        MaxBodySize: 0,
        MinBodySize: 0,
        Regexps: [],
        StatusCode: []
    });
    const [droppedCount, setDroppedCount] = useState(0);

    // state
    const [loading, setLoading] = useState(false)
    const [content, setContent] = useState<FuzzerResponse[]>([])
    const [reqEditor, setReqEditor] = useState<IMonacoEditor>()
    const [fuzzToken, setFuzzToken] = useState("")
    const [search, setSearch] = useState("")
    const [targetUrl, setTargetUrl] = useState("")

    const [refreshTrigger, setRefreshTrigger] = useState(false)
    const refreshRequest = () => {
        setRefreshTrigger(!refreshTrigger)
    }

    // history
    const [history, setHistory] = useState<string[]>([])
    const [currentHistoryIndex, setCurrentHistoryIndex] = useState<number>()

    const [urlPacketShow, setUrlPacketShow] = useState<boolean>(false)

    // filter
    const [keyword, setKeyword] = useState<string>("")
    const [filterContent, setFilterContent] = useState<FuzzerResponse[]>([])
    const [timer, setTimer] = useState<any>()

    useEffect(() => {
        getValue(WEB_FUZZ_HOTPATCH_CODE).then((data: any) => {
            if (!data) {
                return
            }
            setHotPatchCode(`${data}`)
        })
    }, [])

    // 定时器
    const sendTimer = useRef<any>(null)

    const withdrawRequest = useMemoizedFn(() => {
        const targetIndex = history.indexOf(request) - 1
        if (targetIndex >= 0) {
            setRequest(history[targetIndex])
            setCurrentHistoryIndex(targetIndex)
        }
    })
    const forwardRequest = useMemoizedFn(() => {
        const targetIndex = history.indexOf(request) + 1
        if (targetIndex < history.length) {
            setCurrentHistoryIndex(targetIndex)
            setRequest(history[targetIndex])
        }
    })

    const sendToFuzzer = useMemoizedFn((isHttps: boolean, request: string) => {
        ipcRenderer.invoke("send-to-tab", {
            type: "fuzzer",
            data: {isHttps: isHttps, request: request}
        })
    })
    const sendToPlugin = useMemoizedFn((request: Uint8Array, isHTTPS: boolean, response?: Uint8Array) => {
        let m = showDrawer({
            width: "80%",
            content: <HackerPlugin request={request} isHTTPS={isHTTPS} response={response}></HackerPlugin>
        })
    })

    // 从历史记录中恢复
    useEffect(() => {
        if (!historyTask) {
            return
        }

        setRequest(historyTask.Request)
        setIsHttps(historyTask.IsHTTPS)
        setProxy(historyTask.Proxy)
        refreshRequest()
    }, [historyTask])

    useEffect(() => {
        // 缓存全局参数
        getValue(WEB_FUZZ_PROXY).then(e => {
            if (!e) {
                return
            }
            setProxy(`${e}`)
        })

    }, [])

    useEffect(() => {
        if (currentHistoryIndex === undefined) {
            return
        }
        refreshRequest()
    }, [currentHistoryIndex])

    useEffect(() => {
        setIsHttps(!!props.isHttps)
        if (props.request) {
            setRequest(props.request)
            setContent([])
        }
    }, [props.isHttps, props.request])

    const loadHistory = useMemoizedFn((id: number) => {
        setLoading(true)
        setHistory([])
        ipcRenderer.invoke(
            "HTTPFuzzer",
            {HistoryWebFuzzerId: id}, fuzzToken
        ).then(() => {
            ipcRenderer.invoke("GetHistoryHTTPFuzzerTask", {Id: id}).then((data: { OriginRequest: HistoryHTTPFuzzerTask }) => {
                setHistoryTask(data.OriginRequest)
            })
        })
    })

    const submitToHTTPFuzzer = useMemoizedFn(() => {
        // 清楚历史任务的标记
        setHistoryTask(undefined);

        saveValue(WEB_FUZZ_PROXY, proxy)
        setLoading(true)
        if (history.includes(request)) {
            history.splice(history.indexOf(request), 1)
        }
        history.push(request)
        setHistory([...history])

        setDroppedCount(0)
        ipcRenderer.invoke(
            "HTTPFuzzer",
            {
                Request: request,
                ForceFuzz: forceFuzz,
                IsHTTPS: isHttps,
                Concurrent: concurrent,
                PerRequestTimeoutSeconds: timeout,
                NoFixContentLength: noFixContentLength,
                Proxy: proxy,
                ActualAddr: actualHost,
                HotPatchCode: hotPatchCode,
                Filter: getFilter(),
            },
            fuzzToken
        )
    })

    const cancelCurrentHTTPFuzzer = useMemoizedFn(() => {
        ipcRenderer.invoke("cancel-HTTPFuzzer", fuzzToken)
    })

    useEffect(() => {
        const token = randomString(60)
        setFuzzToken(token)

        const dataToken = `${token}-data`
        const errToken = `${token}-error`
        const endToken = `${token}-end`

        ipcRenderer.on(errToken, (e, details) => {
            notification["error"]({
                message: `提交模糊测试请求失败 ${details}`,
                placement: "bottomRight"
            })
        })
        let buffer: FuzzerResponse[] = []
        let droppedCount = 0;
        let count: number = 0
        const updateData = () => {
            if (buffer.length <= 0) {
                return
            }
            if (JSON.stringify(buffer) !== JSON.stringify(content)) setContent([...buffer])
        }

        ipcRenderer.on(dataToken, (e: any, data: any) => {
            if (data["MatchedByFilter"] !== true && !filterIsEmpty(getFilter())) {
                // 不匹配的
                droppedCount++
                setDroppedCount(droppedCount)
                return
            }
            // const response = new Buffer(data.ResponseRaw).toString(fixEncoding(data.GuessResponseEncoding))
            buffer.push({
                StatusCode: data.StatusCode,
                Ok: data.Ok,
                Reason: data.Reason,
                Method: data.Method,
                Host: data.Host,
                ContentType: data.ContentType,
                Headers: (data.Headers || []).map((i: any) => {
                    return {Header: i.Header, Value: i.Value}
                }),
                DurationMs: data.DurationMs,
                BodyLength: data.BodyLength,
                UUID: data.UUID,
                Timestamp: data.Timestamp,
                ResponseRaw: data.ResponseRaw,
                RequestRaw: data.RequestRaw,
                Payloads: data.Payloads,
                IsHTTPS: data.IsHTTPS,
                Count: count,
                BodySimilarity: data.BodySimilarity,
                HeaderSimilarity: data.HeaderSimilarity,
            } as FuzzerResponse)
            count++
            // setContent([...buffer])
        })
        ipcRenderer.on(endToken, () => {
            updateData()
            buffer = []
            count = 0
            droppedCount = 0
            setLoading(false)
        })

        const updateDataId = setInterval(() => {
            updateData()
        }, 200)

        return () => {
            ipcRenderer.invoke("cancel-HTTPFuzzer", token)

            clearInterval(updateDataId)
            ipcRenderer.removeAllListeners(errToken)
            ipcRenderer.removeAllListeners(dataToken)
            ipcRenderer.removeAllListeners(endToken)
        }
    }, [])

    const searchContent = (keyword: string) => {
        if (timer) {
            clearTimeout(timer)
            setTimer(null)
        }
        setTimer(
            setTimeout(() => {
                try {
                    const filters = content.filter((item) => {
                        return Buffer.from(item.ResponseRaw).toString("utf8").match(new RegExp(keyword, "g"))
                    })
                    setFilterContent(filters)
                } catch (error) {
                }
            }, 500)
        )
    }

    const downloadContent = useMemoizedFn(() => {
        if (!keyword) {
            failed('请先输入需要搜索的关键词')
            return
        }

        const strs = []
        try {
            const reg = new RegExp(keyword)
            for (let info of filterContent) {
                let str = Buffer.from(info.ResponseRaw).toString('utf8')
                let temp: any
                while ((temp = reg.exec(str)) !== null) {
                    // @ts-ignore
                    if (temp[1]) {
                        // @ts-ignore
                        strs.push(temp[1])
                        str = str.substring(temp['index'] + 1)
                        reg.lastIndex = 0
                    } else {
                        break
                    }
                }
            }
        } catch (error) {
            failed("正则有问题,请检查后重新输入")
            return
        }

        if (strs.length === 0) {
            failed('未捕获到关键词信息')
            return
        }

        ipcRenderer.invoke("show-save-dialog", 'fuzzer列表命中内容.txt').then((res) => {
            if (res.canceled) return

            ipcRenderer
                .invoke("write-file", {
                    route: res.filePath,
                    data: strs.join('\n\r')
                })
                .then(() => {
                    success('下载完成')
                    ipcRenderer.invoke("open-specified-file", res.filePath)
                })
        })
    })

    useEffect(() => {
        if (!!keyword) {
            searchContent(keyword)
        } else {
            setFilterContent([])
        }
    }, [keyword])

    useEffect(() => {
        if (keyword && content.length !== 0) {
            const filters = content.filter(item => {
                return Buffer.from(item.ResponseRaw).toString("utf8").match(new RegExp(keyword, 'g'))
            })
            setFilterContent(filters)
        }
    }, [content])

    const onlyOneResponse = !loading && (content || []).length === 1

    const filtredResponses =
        search === ""
            ? content || []
            : (content || []).filter((i) => {
                return Buffer.from(i.ResponseRaw).toString().includes(search)
            })
    const successResults = filtredResponses.filter((i) => i.Ok)
    const failedResults = filtredResponses.filter((i) => !i.Ok)

    const sendFuzzerSettingInfo = useMemoizedFn(() => {
        const info: fuzzerInfoProp = {
            time: new Date().getTime().toString(),
            isHttps: isHttps,
            forceFuzz: forceFuzz,
            concurrent: concurrent,
            proxy: proxy,
            actualHost: actualHost,
            timeout: timeout,
            request: request
        }
        if (sendTimer.current) {
            clearTimeout(sendTimer.current)
            sendTimer.current = null
        }
        sendTimer.current = setTimeout(() => {
            ipcRenderer.invoke('send-fuzzer-setting-data', {key: props.order || "", param: JSON.stringify(info)})
        }, 1000);
    })
    useEffect(() => {
        sendFuzzerSettingInfo()
    }, [isHttps, forceFuzz, concurrent, proxy, actualHost, timeout, request])

    const responseViewer = useMemoizedFn((rsp: FuzzerResponse) => {
        return (
            <HTTPPacketEditor
                system={props.system}
                originValue={rsp.ResponseRaw}
                bordered={true}
                hideSearch={true}
                emptyOr={
                    !rsp?.Ok && (
                        <Result
                            status={"error"}
                            title={"请求失败"}
                            // no such host
                            subTitle={(() => {
                                const reason = content[0]!.Reason
                                if (reason.includes("tcp: i/o timeout")) {
                                    return "网络超时"
                                }
                                if (reason.includes("no such host")) {
                                    return "DNS 错误或主机错误"
                                }
                                return undefined
                            })()}
                        >
                            <>详细原因:{rsp.Reason}</>
                        </Result>
                    )
                }
                readOnly={true}
                extra={
                    (
                        <Space>
                            {loading && <Spin size={"small"} spinning={loading}/>}
                            {onlyOneResponse ? (
                                <Space>
                                    {content[0].IsHTTPS && <Tag>{content[0].IsHTTPS ? "https" : ""}</Tag>}
                                    <Tag>{content[0].DurationMs}ms</Tag>
                                    <Space key='single'>
                                        <Button
                                            size={"small"}
                                            onClick={() => {
                                                analyzeFuzzerResponse(
                                                    rsp,
                                                    (bool, r) => {
                                                        // setRequest(r)
                                                        // refreshRequest()
                                                    }
                                                )
                                            }}
                                            type={"primary"}
                                            icon={<ProfileOutlined/>}
                                        >
                                            详情
                                        </Button>
                                        <Button
                                            type={"primary"}
                                            size={"small"}
                                            onClick={() => {
                                                setContent([])
                                            }}
                                            danger={true}
                                            icon={<DeleteOutlined/>}
                                        />
                                    </Space>
                                </Space>
                            ) : (
                                <Space key='list'>
                                    <Tag color={"green"}>成功:{successResults.length}</Tag>
                                    <Input
                                        size={"small"}
                                        value={search}
                                        onChange={(e) => {
                                            setSearch(e.target.value)
                                        }}
                                    />
                                    {/*<Tag>当前请求结果数[{(content || []).length}]</Tag>*/}
                                    <Button
                                        size={"small"}
                                        onClick={() => {
                                            setContent([])
                                        }}
                                    >
                                        清除数据
                                    </Button>
                                </Space>
                            )}

                        </Space>
                    )
                }
            />
        )
    })

    const hotPatchTrigger = useMemoizedFn(() => {
        let m = showModal({
            title: "调试 / 插入热加载代码",
            width: "60%",
            content: (
                <div>
                    <HTTPFuzzerHotPatch initialHotPatchCode={hotPatchCode || ""} onInsert={tag => {
                        if (reqEditor) monacoEditorWrite(reqEditor, tag);
                        m.destroy()
                    }} onSaveCode={code => {
                        setHotPatchCode(code)
                        saveValue(WEB_FUZZ_HOTPATCH_CODE, code)
                    }}/>
                </div>
            )
        })
    })

    return (
        <div style={{height: "100%", width: "100%", display: "flex", flexDirection: "column", overflow: "hidden"}}>
            <Row gutter={8} style={{marginBottom: 8}}>
                <Col span={24} style={{textAlign: "left", marginTop: 4}}>
                    <Space>
                        {loading ? (
                            <Button
                                style={{width: 150}}
                                onClick={() => {
                                    cancelCurrentHTTPFuzzer()
                                }}
                                // size={"small"}
                                danger={true}
                                type={"primary"}
                            >
                                强制停止
                            </Button>
                        ) : (
                            <Button
                                style={{width: 150}}
                                onClick={() => {
                                    setContent([])
                                    setRedirectedResponse(undefined)
                                    sendFuzzerSettingInfo()
                                    submitToHTTPFuzzer()
                                }}
                                // size={"small"}
                                type={"primary"}
                            >
                                发送数据包
                            </Button>
                        )}
                        <Space>
                            <Button
                                onClick={() => {
                                    withdrawRequest()
                                }}
                                type={"link"}
                                icon={<LeftOutlined/>}
                            />
                            <Button
                                onClick={() => {
                                    forwardRequest()
                                }}
                                type={"link"}
                                icon={<RightOutlined/>}
                            />
                            {history.length > 1 && (
                                <Dropdown
                                    trigger={["click"]}
                                    overlay={() => {
                                        return (
                                            <Menu>
                                                {history.map((i, index) => {
                                                    return (
                                                        <Menu.Item
                                                            style={{width: 120}}
                                                            onClick={() => {
                                                                setRequest(i)
                                                                setCurrentHistoryIndex(index)
                                                            }}
                                                        >{`${index}`}</Menu.Item>
                                                    )
                                                })}
                                            </Menu>
                                        )
                                    }}
                                >
                                    <Button size={"small"} type={"link"} onClick={(e) => e.preventDefault()}>
                                        History <DownOutlined/>
                                    </Button>
                                </Dropdown>
                            )}
                        </Space>
                        <Checkbox defaultChecked={isHttps} value={isHttps} onChange={() => setIsHttps(!isHttps)}>强制
                            HTTPS</Checkbox>
                        <SwitchItem
                            label={"高级配置"}
                            formItemStyle={{marginBottom: 0}}
                            value={advancedConfig}
                            setValue={setAdvancedConfig}
                            size={"small"}
                        />
                        {droppedCount > 0 && <Tag color={"red"}>已丢弃[{droppedCount}]个响应</Tag>}
                        {onlyOneResponse && content[0].Ok && (
                            <Form.Item style={{marginBottom: 0}}>
                                <Button
                                    onClick={() => {
                                        setLoading(true)
                                        ipcRenderer
                                            .invoke("RedirectRequest", {
                                                Request: request,
                                                Response: new Buffer(content[0].ResponseRaw).toString("utf8"),
                                                IsHttps: isHttps,
                                                PerRequestTimeoutSeconds: timeout,
                                                NoFixContentLength: noFixContentLength,
                                                Proxy: proxy
                                            })
                                            .then((rsp: FuzzerResponse) => {
                                                setRedirectedResponse(rsp)
                                            })
                                            .catch((e) => {
                                                failed(`"ERROR in: ${e}"`)
                                            })
                                            .finally(() => {
                                                setTimeout(() => setLoading(false), 300)
                                            })
                                    }}
                                >
                                    跟随重定向
                                </Button>
                            </Form.Item>
                        )}
                        {loading && (
                            <Space>
                                <Spin size={"small"}/>
                                <div style={{color: "#3a8be3"}}>sending packets</div>
                            </Space>
                        )}
                        {proxy && <Tag>代理:{proxy}</Tag>}
                        {/*<Popover*/}
                        {/*    trigger={"click"}*/}
                        {/*    content={*/}
                        {/*    }*/}
                        {/*>*/}
                        {/*    <Button type={"link"} size={"small"}>*/}
                        {/*        配置请求包*/}
                        {/*    </Button>*/}
                        {/*</Popover>*/}
                        {actualHost !== "" && <Tag color={"red"}>请求 Host:{actualHost}</Tag>}
                    </Space>
                </Col>
                {/*<Col span={12} style={{textAlign: "left"}}>*/}
                {/*</Col>*/}
            </Row>

            {advancedConfig && (
                <Row style={{marginBottom: 8}} gutter={8}>
                    <Col span={16}>
                        {/*高级配置*/}
                        <Card bordered={true} size={"small"} bodyStyle={{height: 106}}>
                            <Spin style={{width: "100%"}} spinning={!reqEditor}>
                                <Form
                                    onSubmitCapture={(e) => e.preventDefault()}
                                    // layout={"horizontal"}
                                    size={"small"}
                                    // labelCol={{span: 8}}
                                    // wrapperCol={{span: 16}}
                                >
                                    <Row gutter={8}>
                                        <Col span={12} xl={8}>
                                            <Form.Item
                                                label={<OneLine width={68}>Intruder</OneLine>}
                                                style={{marginBottom: 4}}
                                            >
                                                <Button
                                                    style={{backgroundColor: "#08a701"}}
                                                    size={"small"}
                                                    type={"primary"}
                                                    onClick={() => {
                                                        const m = showModal({
                                                            width: "70%",
                                                            content: (
                                                                <>
                                                                    <StringFuzzer
                                                                        advanced={true}
                                                                        disableBasicMode={true}
                                                                        insertCallback={(template: string) => {
                                                                            if (!template) {
                                                                                Modal.warn({
                                                                                    title: "Payload 为空 / Fuzz 模版为空"
                                                                                })
                                                                            } else {
                                                                                if (reqEditor && template) {
                                                                                    reqEditor.trigger(
                                                                                        "keyboard",
                                                                                        "type",
                                                                                        {
                                                                                            text: template
                                                                                        }
                                                                                    )
                                                                                } else {
                                                                                    Modal.error({
                                                                                        title: "BUG: 编辑器失效"
                                                                                    })
                                                                                }
                                                                                m.destroy()
                                                                            }
                                                                        }}
                                                                    />
                                                                </>
                                                            )
                                                        })
                                                    }}
                                                >
                                                    插入 yak.fuzz 语法
                                                </Button>
                                            </Form.Item>
                                        </Col>
                                        <Col span={12} xl={8}>
                                            <SwitchItem
                                                label={<OneLine width={68}>渲染 fuzz</OneLine>}
                                                setValue={(e) => {
                                                    if (!e) {
                                                        Modal.confirm({
                                                            title: "确认关闭 Fuzz 功能吗?关闭之后,所有的 Fuzz 标签将会失效",
                                                            onOk: () => {
                                                                setForceFuzz(e)
                                                            }
                                                        })
                                                        return
                                                    }
                                                    setForceFuzz(e)
                                                }}
                                                size={"small"}
                                                value={forceFuzz}
                                                formItemStyle={{marginBottom: 4}}
                                            />
                                        </Col>
                                        <Col span={12} xl={8}>
                                            <InputInteger
                                                label={<OneLine width={68}>并发线程</OneLine>}
                                                size={"small"}
                                                setValue={(e) => {
                                                    setConcurrent(e)
                                                }}
                                                formItemStyle={{marginBottom: 4}} // width={40}
                                                width={50}
                                                value={concurrent}
                                            />
                                        </Col>
                                        <Col span={12} xl={8}>
                                            <SwitchItem
                                                label={<OneLine width={68}>HTTPS</OneLine>}
                                                setValue={(e) => {
                                                    setIsHttps(e)
                                                }}
                                                size={"small"}
                                                value={isHttps}
                                                formItemStyle={{marginBottom: 4}}
                                            />
                                        </Col>
                                        <Col span={12} xl={8}>
                                            <SwitchItem
                                                label={<OneLine width={70}>
                                                    <Tooltip title={"不修复 Content-Length: 常用发送多个数据包"}>
                                                        不修复长度
                                                    </Tooltip>
                                                </OneLine>}
                                                setValue={(e) => {
                                                    setNoFixContentLength(e)
                                                }}
                                                size={"small"}
                                                value={noFixContentLength}
                                                formItemStyle={{marginBottom: 4}}
                                            />
                                        </Col>
                                        <Col span={12} xl={8}>
                                            <ItemSelects
                                                item={{
                                                    style: {marginBottom: 4},
                                                    label: <OneLine width={68}>设置代理</OneLine>,
                                                }}
                                                select={{
                                                    style: {width: "100%"},
                                                    allowClear: true,
                                                    autoClearSearchValue: true,
                                                    maxTagTextLength: 8,
                                                    mode: "tags",
                                                    data: [
                                                        {text: "http://127.0.0.1:7890", value: "http://127.0.0.1:7890"},
                                                        {text: "http://127.0.0.1:8080", value: "http://127.0.0.1:8080"},
                                                        {text: "http://127.0.0.1:8082", value: "http://127.0.0.1:8082"}
                                                    ],
                                                    value: proxy ? proxy.split(",") : [],
                                                    setValue: (value) => setProxy(value.join(",")),
                                                    maxTagCount: "responsive",
                                                }}
                                            ></ItemSelects>
                                            {/* <ManyMultiSelectForString
                                                formItemStyle={{marginBottom: 4}}
                                                label={<OneLine width={68}>设置代理</OneLine>}
                                                data={[
                                                    "http://127.0.0.1:7890",
                                                    "http://127.0.0.1:8080",
                                                    "http://127.0.0.1:8082"
                                                ].map((i) => {
                                                    return {label: i, value: i}
                                                })}
                                                mode={"tags"}
                                                defaultSep={","}
                                                value={proxy}
                                                setValue={(r) => {
                                                    setProxy(r.split(",").join(","))
                                                }}
                                            /> */}
                                        </Col>
                                        <Col span={12} xl={8}>
                                            <InputItem
                                                extraFormItemProps={{
                                                    style: {marginBottom: 0}
                                                }}
                                                label={<OneLine width={68}>请求 Host</OneLine>}
                                                setValue={setActualHost}
                                                value={actualHost}
                                            />
                                        </Col>
                                        <Col span={12} xl={8}>
                                            <InputFloat
                                                formItemStyle={{marginBottom: 4}}
                                                size={"small"}
                                                label={<OneLine width={68}>超时时间</OneLine>}
                                                setValue={setParamTimeout}
                                                value={timeout}
                                            />
                                        </Col>
                                    </Row>
                                </Form>
                            </Spin>
                        </Card>
                    </Col>
                    <Col span={8}>
                        <AutoCard title={<Tooltip title={"通过过滤匹配,丢弃无用数据包,保证界面性能!"}>
                            设置过滤器
                        </Tooltip>}
                                  bordered={false} size={"small"} bodyStyle={{paddingTop: 4}}
                                  style={{marginTop: 0, paddingTop: 0}}
                        >
                            <Form size={"small"} onSubmitCapture={e => e.preventDefault()}>
                                <Row gutter={20}>
                                    <Col span={12}>
                                        <InputItem
                                            label={"状态码"} placeholder={"200,300-399"}
                                            disable={loading}
                                            value={getFilter().StatusCode.join(",")}
                                            setValue={e => {
                                                setFilter({...getFilter(), StatusCode: e.split(",").filter(i => !!i)})
                                            }}
                                            extraFormItemProps={{style: {marginBottom: 0}}}
                                        />
                                    </Col>
                                    <Col span={12}>
                                        <InputItem
                                            label={"关键字"} placeholder={"Login,登录成功"}
                                            value={getFilter().Keywords.join(",")}
                                            disable={loading}
                                            setValue={e => {
                                                setFilter({...getFilter(), Keywords: e.split(",").filter(i => !!i)})
                                            }}
                                            extraFormItemProps={{style: {marginBottom: 0}}}
                                        />
                                    </Col>
                                    <Col span={12}>
                                        <InputItem
                                            label={"正则"} placeholder={`Welcome\\s+\\w+!`}
                                            value={getFilter().Regexps.join(",")}
                                            disable={loading}
                                            setValue={e => {
                                                setFilter({...getFilter(), Regexps: e.split(",").filter(i => !!i)})
                                            }}
                                            extraFormItemProps={{style: {marginBottom: 0, marginTop: 2}}}
                                        />
                                    </Col>
                                </Row>
                            </Form>
                        </AutoCard>
                    </Col>
                </Row>
            )}
            {/*<Divider style={{marginTop: 6, marginBottom: 8, paddingTop: 0}}/>*/}
            <ResizeBox
                firstMinSize={350} secondMinSize={360}
                style={{overflow: "hidden"}}
                firstNode={<HTTPPacketEditor
                    system={props.system}
                    refreshTrigger={refreshTrigger}
                    hideSearch={true}
                    bordered={true}
                    originValue={new Buffer(request)}
                    actions={[
                        {
                            id: "packet-from-url",
                            label: "URL转数据包",
                            contextMenuGroupId: "1_urlPacket",
                            run: () => {
                                setUrlPacketShow(true)
                            }
                        },
                        {
                            id: "copy-as-url",
                            label: "复制为 URL",
                            contextMenuGroupId: "1_urlPacket",
                            run: () => {
                                copyAsUrl({Request: getRequest(), IsHTTPS: getIsHttps()})
                            }
                        },
                        {
                            id: "insert-intruder-tag",
                            label: "插入模糊测试字典标签",
                            contextMenuGroupId: "1_urlPacket",
                            run: (editor) => {
                                showDictsAndSelect(i => {
                                    monacoEditorWrite(editor, i, editor.getSelection())
                                })
                            }
                        },
                        {
                            id: "insert-hotpatch-tag",
                            label: "插入热加载标签",
                            contextMenuGroupId: "1_urlPacket",
                            run: (editor) => {
                                hotPatchTrigger()
                            }
                        },
                    ]}
                    onEditor={setReqEditor}
                    onChange={(i) => setRequest(new Buffer(i).toString("utf8"))}
                    extra={
                        <Space size={2}>
                            <Button
                                style={{marginRight: 1}}
                                size={"small"} type={"primary"}
                                onClick={() => {
                                    hotPatchTrigger()
                                }}
                            >热加载标签</Button>
                            <Popover
                                trigger={"click"}
                                title={"从 URL 加载数据包"}
                                content={
                                    <div style={{width: 400}}>
                                        <Form
                                            layout={"vertical"}
                                            onSubmitCapture={(e) => {
                                                e.preventDefault()

                                                ipcRenderer
                                                    .invoke("Codec", {
                                                        Type: "packet-from-url",
                                                        Text: targetUrl
                                                    })
                                                    .then((e) => {
                                                        if (e?.Result) {
                                                            setRequest(e.Result)
                                                            refreshRequest()
                                                        }
                                                    })
                                                    .finally(() => {
                                                    })
                                            }}
                                            size={"small"}
                                        >
                                            <InputItem
                                                label={"从 URL 构造请求"}
                                                value={targetUrl}
                                                setValue={setTargetUrl}
                                                extraFormItemProps={{style: {marginBottom: 8}}}
                                            ></InputItem>
                                            <Form.Item style={{marginBottom: 8}}>
                                                <Button type={"primary"} htmlType={"submit"}>
                                                    构造请求
                                                </Button>
                                            </Form.Item>

                                        </Form>
                                    </div>
                                }
                            >
                                <Button size={"small"} type={"primary"}>
                                    URL
                                </Button>
                            </Popover>
                            <Popover
                                trigger={"click"}
                                placement={"bottom"}
                                destroyTooltipOnHide={true}
                                content={
                                    <div style={{width: 400}}>
                                        <HTTPFuzzerHistorySelector onSelect={e => {
                                            loadHistory(e)
                                        }}/>
                                    </div>
                                }
                            >
                                <Button size={"small"} type={"primary"} icon={<HistoryOutlined/>}>
                                    历史
                                </Button>
                            </Popover>
                        </Space>
                    }
                />}
                secondNode={<AutoSpin spinning={false}>
                    {onlyOneResponse ? (
                        <>{redirectedResponse ? responseViewer(redirectedResponse) : responseViewer(content[0])}</>
                    ) : (
                        <>
                            {(content || []).length > 0 ? (
                                <HTTPFuzzerResultsCard
                                    onSendToWebFuzzer={sendToFuzzer}
                                    sendToPlugin={sendToPlugin}
                                    setRequest={(r) => {
                                        setRequest(r)
                                        refreshRequest()
                                    }}
                                    extra={
                                        <div>
                                            <Input
                                                value={keyword}
                                                style={{maxWidth: 200}}
                                                allowClear
                                                placeholder="输入字符串或正则表达式"
                                                onChange={e => setKeyword(e.target.value)}
                                                addonAfter={
                                                    <DownloadOutlined style={{cursor: "pointer"}}
                                                                      onClick={downloadContent}/>
                                                }></Input>
                                        </div>
                                    }
                                    failedResponses={failedResults}
                                    successResponses={filterContent.length !== 0 ? filterContent : keyword ? [] : successResults}
                                />
                            ) : (
                                <Result
                                    status={"info"}
                                    title={"请在左边编辑并发送一个 HTTP 请求/模糊测试"}
                                    subTitle={
                                        "本栏结果针对模糊测试的多个 HTTP 请求结果展示做了优化,可以自动识别单个/多个请求的展示"
                                    }
                                />
                            )}
                        </>
                    )}
                </AutoSpin>}/>
            <Modal
                visible={urlPacketShow}
                title='从 URL 加载数据包'
                onCancel={() => setUrlPacketShow(false)}
                footer={null}
            >
                <Form
                    layout={"vertical"}
                    onSubmitCapture={(e) => {
                        e.preventDefault()

                        ipcRenderer
                            .invoke("Codec", {
                                Type: "packet-from-url",
                                Text: targetUrl
                            })
                            .then((e) => {
                                if (e?.Result) {
                                    setRequest(e.Result)
                                    refreshRequest()
                                    setUrlPacketShow(false)
                                }
                            })
                            .finally(() => {
                            })
                    }}
                    size={"small"}
                >
                    <InputItem
                        label={"从 URL 构造请求"}
                        value={targetUrl}
                        setValue={setTargetUrl}
                        extraFormItemProps={{style: {marginBottom: 8}}}
                    ></InputItem>
                    <Form.Item style={{marginBottom: 8}}>
                        <Button type={"primary"} htmlType={"submit"}>
                            构造请求
                        </Button>
                    </Form.Item>
                </Form>
            </Modal>
        </div>
    )
}
Example #18
Source File: SelectBox.tsx    From posthog-foss with MIT License 4 votes vote down vote up
export function SelectUnit({
    dropdownLogic,
    items,
    disablePopover = false,
}: {
    dropdownLogic: selectBoxLogicType & BuiltLogic
    items: Record<string, SelectBoxItem>
    disablePopover?: boolean // Disable PropertyKeyInfo popover
}): JSX.Element {
    const { setSelectedItem, clickSelectedItem } = useActions(dropdownLogic)
    const { selectedItem, search, blockMouseOver } = useValues(dropdownLogic)
    const [hiddenData, setHiddenData] = useState<Record<string, SelectedItem[]>>({})
    const [data, setData] = useState<Record<string, SelectedItem[]>>({})
    const [flattenedData, setFlattenedData] = useState<SelectedItem[]>([])
    const [groupTypes, setGroupTypes] = useState<string[]>([])

    let lengthOfData = 0
    Object.values(items).forEach((entry) => {
        lengthOfData += entry?.dataSource?.length || 0
    })

    useEffect(() => {
        const formattedData: Record<string, SelectedItem[]> = {}
        const _hiddenData: Record<string, SelectedItem[]> = {}
        const _groupTypes: string[] = []
        const currHidden = Object.keys(hiddenData)
        Object.keys(items).forEach((groupName) => {
            if (!currHidden.includes(groupName)) {
                formattedData[groupName] = items[groupName].dataSource
            } else {
                formattedData[groupName] = []
                _hiddenData[groupName] = items[groupName].dataSource
            }
            _groupTypes.push(groupName)
        })
        setGroupTypes(_groupTypes)
        setData(formattedData)
        setHiddenData(_hiddenData)
    }, [lengthOfData])

    useEffect(() => {
        const _flattenedData: SelectedItem[] = []
        Object.keys(data).forEach((key) => {
            _flattenedData.push({
                key: key,
                name: key,
                groupName: key,
            })
            _flattenedData.push(...data[key].map((selectItem) => ({ ...selectItem, groupName: key })))
        })
        setFlattenedData(_flattenedData)
    }, [data])

    const hideKey = (key: string): void => {
        const { [`${key}`]: hideItem } = data
        const copy = {
            ...data,
        }
        copy[key] = []
        setData(copy)
        setHiddenData({
            ...hiddenData,
            [`${key}`]: hideItem,
        })
    }

    const showKey = (key: string): void => {
        const { [`${key}`]: showItem, ...restOfData } = hiddenData
        setHiddenData(restOfData)
        const copy = {
            ...data,
        }
        copy[key] = showItem
        setData(copy)
    }

    const renderItem: ListRowRenderer = ({ index, style }: ListRowProps) => {
        const item = flattenedData[index]

        if (!item.groupName) {
            return null
        }

        const group = items[item.groupName]
        if (groupTypes.includes(item.key)) {
            return (
                <div style={style} key={item.key}>
                    <span onClick={() => (hiddenData?.[item.key] ? showKey(item.key) : hideKey(item.key))}>
                        <h4 style={{ cursor: 'pointer', userSelect: 'none', padding: '4px 12px', marginBottom: 0 }}>
                            {hiddenData?.[item.key] || !data[item.key].length ? <RightOutlined /> : <DownOutlined />}{' '}
                            {group.header(item.key)}
                            <span
                                style={{
                                    float: 'right',
                                    fontWeight: search && flattenedData.length > 0 ? 700 : 'normal',
                                }}
                                className="text-small"
                            >
                                {data?.[item.key]?.length || hiddenData?.[item.key]?.length || '0'}{' '}
                                {flattenedData.length === 1 ? 'entry' : 'entries'}
                            </span>
                        </h4>
                    </span>
                </div>
            )
        } else {
            return (
                <List.Item
                    className={selectedItem?.key === item.key ? 'selected' : undefined}
                    key={item.key}
                    onClick={() => clickSelectedItem(item, group)}
                    style={style}
                    onMouseOver={() =>
                        !blockMouseOver && setSelectedItem({ ...item, key: item.key, category: group.type })
                    }
                    data-attr={`select-item-${index}`}
                >
                    <PropertyKeyInfo value={item.name} disablePopover={disablePopover} />
                </List.Item>
            )
        }
    }

    return (
        <>
            {flattenedData.length > 0 && (
                <div style={{ height: '100%' }}>
                    {
                        <AutoSizer>
                            {({ height, width }: { height: number; width: number }) => {
                                return (
                                    <VirtualizedList
                                        height={height}
                                        overscanRowCount={0}
                                        rowCount={flattenedData.length}
                                        rowHeight={35}
                                        rowRenderer={renderItem}
                                        width={width}
                                        tabIndex={-1}
                                    />
                                )
                            }}
                        </AutoSizer>
                    }
                </div>
            )}
        </>
    )
}
Example #19
Source File: ShareTicketModal.tsx    From condo with MIT License 4 votes vote down vote up
ShareTicketModal: React.FC<IShareTicketModalProps> = (props) => {
    const intl = useIntl()
    const SendTicketToEmailMessage = intl.formatMessage({ id: 'SendTicketToEmail' })
    const ToEmployeesEmailMessage = intl.formatMessage({ id: 'ToEmployeesEmail' })
    const EmployeesNameMessage = intl.formatMessage({ id: 'EmployeesName' })
    const ServerErrorMessage = intl.formatMessage({ id: 'ServerError' })
    const WhatsappMessage = intl.formatMessage({ id: 'WhatsApp' })
    const TelegramMessage = intl.formatMessage({ id: 'Telegram' })
    const ShareHeaderMessage = intl.formatMessage({ id: 'ticket.shareHeader' })
    const ShareButtonMessage = intl.formatMessage({ id: 'ticket.shareButton' })
    const OKMessage = intl.formatMessage({ id: 'OK' })
    const ShareSentMessage = intl.formatMessage({ id: 'ticket.shareSent' })
    const ShareSentToEmailMessage = intl.formatMessage({ id: 'ticket.shareSentToEmail' })
    const { isSmall } = useLayoutContext()

    const { date, number, details, id, locale, organization } = props
    const cipher = crypto.createCipher(ALGORITHM, SALT)

    let cutDetails = details || ''
    if (cutDetails.length >= 110) {
        cutDetails = `${cutDetails.substr(0, 100)}…`
    }
    const stringifiedParams = JSON.stringify({ date, number, details: cutDetails, id })
    const encryptedText = cipher.update(stringifiedParams, 'utf8', CRYPTOENCODING) + cipher.final(CRYPTOENCODING)

    const { query } = useRouter()
    const [shareTicket] = useMutation(SHARE_TICKET_MUTATION)

    const {
        publicRuntimeConfig: { serverUrl: origin },
    } = getConfig()

    const [chosenEmployees, setChosenEmployees] = useState([])
    const [loading, setLoading] = useState(false)
    const [shareVisible, setShareVisible] = useState(false)
    const [okVisible, setOkVisible] = useState(false)
    const [usersWithoutEmail, setUsersWithoutEmail] = useState([])

    const parseSelectValue = (selectedEmployees) => {
        try {
            return selectedEmployees.map(JSON.parse)
        } catch (error) {
            console.error('Invalid format for employees in multiple select', selectedEmployees)
        }
    }

    function handleSelect (value) {
        const withoutEmails = parseSelectValue(value).filter(item => !get(item, 'value.hasEmail')).map(item => item.text)
        setUsersWithoutEmail(withoutEmails)
        setChosenEmployees(value)
    }

    async function handleClick () {
        setLoading(true)
        const sender = getClientSideSenderInfo()
        const { data, error } = await shareTicket({
            variables: {
                data: {
                    sender,
                    employees: parseSelectValue(chosenEmployees).filter(employee => get(employee, 'value.hasEmail')).map(employee => employee.id),
                    ticketId: query.id,
                },
            },
        })
        if (data && data.obj) {
            setChosenEmployees([])
            setShareVisible(false)
            setOkVisible(true)
            setUsersWithoutEmail([])
        }
        if (error) {
            console.error(error)
            notification.error({
                message: ServerErrorMessage,
                description: error.message,
            })
        }
        setLoading(false)
    }

    function handleCancel () {
        setShareVisible(false)
    }

    function handleShow () {
        setShareVisible(true)
    }

    function handleClickSecond () {
        setOkVisible(false)
    }

    return (
        <>
            <Button
                type={'sberDefaultGradient'}
                icon={<ShareAltOutlined />}
                secondary
                onClick={handleShow}
                css={sendButton}
            >
                {ShareButtonMessage}
            </Button>
            <Modal
                style={{ top: 30 }}
                visible={okVisible}
                footer={<Button
                    type='sberPrimary'
                    size='large'
                    onClick={handleClickSecond}
                >
                    {OKMessage}
                </Button>}
                onCancel={handleCancel}
                title={ShareSentMessage}
            >
                {ShareSentToEmailMessage}
            </Modal>
            <Modal
                style={{ top: 30 }}
                visible={shareVisible}
                footer={null}
                onCancel={handleCancel}
                title={<Typography.Title level={isSmall ? 5 : 3}>{ShareHeaderMessage}</Typography.Title>}
            >
                <Row gutter={[0, 16]}>
                    <Col span={24}>
                        <a
                            target='_blank'
                            rel='noreferrer'
                            href={`https://wa.me/?text=${encodeURIComponent(`${origin}/share?q=${encryptedText}&locale=${locale || EN_LOCALE}`)}`}
                        >
                            <ShareButton>
                                {WhatsappMessage}
                                <RightOutlined />
                            </ShareButton>
                        </a>
                    </Col>
                    <Col span={24}>
                        <a
                            target='_blank'
                            rel='noreferrer'
                            href={`https://t.me/share/url?url=${encodeURIComponent(`${origin}/share?q=${encryptedText}&locale=${locale || EN_LOCALE}`)}`}
                        >
                            <ShareButton>
                                {TelegramMessage}
                                <RightOutlined />
                            </ShareButton>
                        </a>
                    </Col>
                    <Col span={24}>
                        <Collapse expandIconPosition='right' css={collapse}>
                            <Collapse.Panel key='1' header={ToEmployeesEmailMessage}>
                                <GraphQlSearchInput
                                    search={getEmployeeWithEmail(get(organization, 'id'))}
                                    showArrow={false}
                                    mode='multiple'
                                    css={search}
                                    onChange={handleSelect}
                                    value={chosenEmployees}
                                    placeholder={EmployeesNameMessage}
                                    autoClearSearchValue={true}
                                />
                                {
                                    !isEmpty(usersWithoutEmail) &&
                                    <Warning>
                                        {usersWithoutEmail}
                                    </Warning>
                                }
                                {
                                    !isEmpty(chosenEmployees) &&
                                    <Button
                                        type='sberPrimary'
                                        size='large'
                                        onClick={handleClick}
                                        style={{ marginTop: '20px' }}
                                        disabled={loading}
                                    >
                                        {SendTicketToEmailMessage}
                                    </Button>
                                }
                            </Collapse.Panel>
                        </Collapse>
                    </Col>
                </Row>
            </Modal>
        </>
    )
}
Example #20
Source File: Row.tsx    From fe-v5 with Apache License 2.0 4 votes vote down vote up
export default function Row(props: IProps) {
  const { name, row, onToggle, onAddClick, onEditClick, onDeleteClick } = props;
  const [editVisble, setEditVisble] = useState(false);
  const [newName, setNewName] = useState<string>();
  const [deleteVisible, setDeleteVisible] = useState(false);

  return (
    <div className='dashboards-panels-row'>
      <div
        className='dashboards-panels-row-name'
        onClick={() => {
          onToggle();
        }}
      >
        {row.collapsed ? <DownOutlined /> : <RightOutlined />}
        <span style={{ paddingLeft: 6 }}>{name}</span>
      </div>
      <Space>
        <AddPanelIcon
          onClick={() => {
            onAddClick();
          }}
        />
        <SettingOutlined
          onClick={() => {
            setEditVisble(true);
          }}
        />
        <DeleteOutlined
          onClick={() => {
            setDeleteVisible(true);
          }}
        />
        {row.collapsed === false && <HolderOutlined className='dashboards-panels-item-drag-handle' />}
      </Space>
      <Modal
        title='编辑分组'
        visible={editVisble}
        onCancel={() => {
          setEditVisble(false);
        }}
        onOk={() => {
          onEditClick({
            ...row,
            name: newName,
          });
          setEditVisble(false);
        }}
      >
        <div>
          分组名称
          <Input
            value={newName}
            onChange={(e) => {
              setNewName(e.target.value);
            }}
            onPressEnter={() => {
              onEditClick({
                ...row,
                name: newName,
              });
              setEditVisble(false);
            }}
          />
        </div>
      </Modal>
      <Modal
        closable={false}
        visible={deleteVisible}
        onCancel={() => {
          setDeleteVisible(false);
        }}
        footer={[
          <Button
            key='cancel'
            onClick={() => {
              setDeleteVisible(false);
            }}
          >
            取消
          </Button>,
          <Button
            key='ok'
            type='primary'
            onClick={() => {
              onDeleteClick('self');
              setDeleteVisible(false);
            }}
          >
            仅删除分组
          </Button>,
          <Button
            key='all'
            type='primary'
            danger
            onClick={() => {
              onDeleteClick('withPanels');
              setDeleteVisible(false);
            }}
          >
            删除分组和图表
          </Button>,
        ]}
      >
        <div>
          <h3 style={{ fontSize: 16 }}>
            <InfoCircleOutlined style={{ color: '#faad14', marginRight: 10, fontSize: 22, position: 'relative', top: 4 }} /> 删除分组
          </h3>
          <div style={{ marginLeft: 38, fontSize: 14 }}>确定删除该分组吗?</div>
        </div>
      </Modal>
    </div>
  );
}
Example #21
Source File: GeneralSignup.tsx    From next-basics with GNU General Public License v3.0 4 votes vote down vote up
export function GeneralSignup(props: GeneralSignupProps): React.ReactElement {
  const [form] = Form.useForm();
  const runtime = getRuntime();
  const brand = runtime.getBrandSettings();
  const enabledFeatures = runtime.getFeatureFlags();
  const { t } = useTranslation(NS_GENERAL_AUTH);
  const [, setForceUpdate] = useState<any>();

  const passwordConfigMap = {
    default: {
      regex: /^.{6,20}$/,
      description: "请输入6至20位密码",
    },
    strong: {
      regex: /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[^a-zA-Z0-9]).{8,20}$/,
      description: "请输入8至20位密码,且同时包含大小写字母、数字、特殊字符",
    },
    backend: {},
  };
  let passwordLevel: keyof typeof passwordConfigMap = "default"; //特性开关
  useEffect(() => {
    if (enabledFeatures["enable-backend-password-config"]) {
      (async () => {
        passwordLevel = "backend";
        passwordConfigMap[passwordLevel] =
          await UserAdminApi_getPasswordConfig();
      })();
    }
  }, []);

  const MIN_USERNAME_LENGTH = 3; //特性开关
  const MAX_USERNAME_LENGTH = 32; //特性开关
  const usernamePattern = new RegExp(
    `^[A-Za-z0-9][A-Za-z0-9|_\\-\\.]{${MIN_USERNAME_LENGTH - 1},${
      MAX_USERNAME_LENGTH - 1
    }}$`
  );

  const iniviteCodePattern = /^[0-9a-zA-Z]{9}$/;
  const hideInvite = iniviteCodePattern.test(getInviteCode());
  const [isCommonSignup, setIsCommonSignup] = useState(true);

  const [isTermsVisible, setIsTermsVisible] = useState(false);

  function showTerms(): void {
    setIsTermsVisible(true);
  }
  function hideTerms(): void {
    setIsTermsVisible(false);
  }
  function agreeTerms(): void {
    form.setFieldsValue({
      terms: true,
    });
    hideTerms();
  }
  function disagreeTerms(): void {
    form.setFieldsValue({
      terms: false,
    });
    hideTerms();
  }

  const [imageHeight, setImageHeight] = useState(window.innerHeight);
  const onWindowResized = () => {
    if (imageHeight < window.innerHeight) {
      setImageHeight(window.innerHeight);
    }
  };
  useEffect(() => {
    const handleWindowResized = debounce(onWindowResized, 500, {
      leading: false,
    });
    window.addEventListener("resize", handleWindowResized);
    return () => {
      window.removeEventListener("resize", handleWindowResized);
    };
  }, []);

  const timer = useRef<any>();
  const count = useRef<number>(duration);
  const [verifyBtnDisabled, setVerifyBtnDisabled] = useState(true);
  const [content, setContent] = useState(t(K.GET_VERIFY_CODE));
  const [messageId, setMessageId] = useState("");
  const handleVerifyBtnClick = async (
    e: React.MouseEvent<HTMLElement, MouseEvent>
  ) => {
    if (timer.current) return;
    count.current -= 1;
    setContent(t(K.GET_VERIFY_CODE_TIPS, { count: count.current }));
    setVerifyBtnDisabled(true);
    timer.current = setInterval(() => {
      count.current -= 1;
      setContent(t(K.GET_VERIFY_CODE_TIPS, { count: count.current }));
      if (count.current === 0) {
        clearInterval(timer.current);
        timer.current = null;
        count.current = duration;
        setVerifyBtnDisabled(false);
        setContent(t(K.GET_VERIFY_CODE));
      }
    }, 1000);
    const result = await CustomerApi_sendApplicationVerificationCode({
      phone_number: form.getFieldValue("phone"),
    });
    result.message_id && setMessageId(result.message_id);
  };

  const redirect = async (result: Record<string, any>): Promise<void> => {
    runtime.reloadSharedData();
    await runtime.reloadMicroApps();
    resetLegacyIframe();
    authenticate({
      org: result.org,
      username: result.username,
      userInstanceId: result.userInstanceId,
      accessRule: result.accessRule,
    });
    const { state } = getHistory().location;
    const from =
      state && state.from
        ? state.from
        : {
            pathname: "/",
          };
    const redirect = createLocation(from);
    getHistory().push(redirect);
  };

  const onFinish = async (values: Record<string, any>): Promise<void> => {
    values.password = encryptValue(values.password);
    try {
      let result: Record<string, any>;
      if (isCommonSignup && !hideInvite) {
        result = await OrgApi_saaSOrgRegister(
          assign(omit(values, ["terms", "password2"]), {
            message_id: messageId,
          }) as OrgApi_SaaSOrgRegisterRequestBody
        );
      } else {
        result = await AuthApi_register(
          assign(
            omit(values, ["terms", "password2", "username"]),
            hideInvite
              ? { invite: getInviteCode(), name: values["username"] }
              : { name: values["username"] }
          ) as AuthApi_RegisterRequestBody
        );
      }
      if (result.loggedIn) {
        redirect(result);
      }
      message.success(t(K.REGISTER_SUCCESS));
    } catch (error) {
      Modal.error({
        title: t(K.REGISTER_FAILED),
        content:
          isCommonSignup && !hideInvite
            ? t(K.WRONG_VERIFICATION_CODE)
            : t(K.WRONG_INVITE_CODE),
      });
    }
  };

  return (
    <>
      <div className={styles.signupWrapper}>
        <div className={styles.signupHeader}>
          <div className={styles.logoBar}>
            <Link to="/">
              {brand.auth_logo_url ? (
                <img
                  src={brand.auth_logo_url}
                  style={{ height: 32, verticalAlign: "middle" }}
                />
              ) : (
                <Logo height={32} style={{ verticalAlign: "middle" }} />
              )}
            </Link>
          </div>
        </div>
        <div className={styles.signupImg}>
          <img src={loginPng} style={{ height: imageHeight }} />
        </div>
        <div className={styles.signupForm}>
          <Card bordered={false}>
            {!hideInvite &&
              (isCommonSignup ? (
                <a
                  onClick={() => {
                    setIsCommonSignup(false);
                  }}
                  style={{ alignSelf: "flex-end" }}
                  id="JumpToJoinFormLink"
                >
                  {t(K.JOIN_THE_ORGANIZATION)} <RightOutlined />
                </a>
              ) : (
                <a
                  onClick={() => {
                    setIsCommonSignup(true);
                  }}
                  id="JumpToCommonFormLink"
                >
                  <LeftOutlined /> {t(K.REGISTER_COMMONLY)}
                </a>
              ))}
            {!hideInvite && isCommonSignup ? (
              <div className={styles.title}>{t(K.REGISTER_ACCOUNT)}</div>
            ) : (
              <div className={styles.title}>{t(K.REGISTER_AND_JOIN)}</div>
            )}
            <Form name="signupForm" form={form} onFinish={onFinish}>
              <Form.Item
                validateFirst={true}
                name="username"
                rules={[
                  {
                    required: true,
                    message: t(K.USERNAME_TIPS, {
                      minLength: 3,
                      maxLength: 32,
                    }),
                  },
                  {
                    pattern: usernamePattern,
                    message: t(K.USERNAME_TIPS, {
                      minLength: 3,
                      maxLength: 32,
                    }),
                  },
                  {
                    validator: (
                      _: any,
                      value: any,
                      callback: (value?: string) => void
                    ) =>
                      validateMap["airNameValidator"](
                        value,
                        callback,
                        setForceUpdate
                      ),
                  },
                ]}
              >
                <Input
                  prefix={<UserOutlined className={styles.inputPrefixIcon} />}
                  placeholder={t(K.USERNAME)}
                />
              </Form.Item>
              {enabledFeatures["enable-nickname-config"] && hideInvite && (
                <Form.Item validateFirst={false} name="nickname">
                  <Input
                    prefix={
                      <SolutionOutlined className={styles.inputPrefixIcon} />
                    }
                    placeholder={t(K.NICKNAME)}
                  />
                </Form.Item>
              )}
              <Form.Item
                name="email"
                validateFirst={true}
                rules={[
                  { required: true, message: t(K.PLEASE_ENTER_VALID_EMAIL) },
                  { type: "email", message: t(K.PLEASE_ENTER_VALID_EMAIL) },
                  {
                    validator: (
                      _: any,
                      value: any,
                      callback: (value?: string) => void
                    ) =>
                      validateMap["airEmailValidator"](
                        value,
                        callback,
                        setForceUpdate
                      ),
                  },
                ]}
              >
                <Input
                  prefix={<MailOutlined className={styles.inputPrefixIcon} />}
                  type="email"
                  placeholder={t(K.EMAIL)}
                />
              </Form.Item>
              <Form.Item
                validateFirst={true}
                name="password"
                rules={[
                  { required: true, message: t(K.PLEASE_INPUT_PASSWORD) },
                  {
                    pattern: passwordConfigMap[passwordLevel].regex,
                    message: passwordConfigMap[passwordLevel].description,
                  },
                ]}
              >
                <Input
                  prefix={<LockOutlined className={styles.inputPrefixIcon} />}
                  type="password"
                  placeholder={t(K.PASSWORD)}
                />
              </Form.Item>
              <Form.Item
                dependencies={["password"]}
                name="password2"
                rules={[
                  { required: true, message: t(K.PLEASE_INPUT_PASSWORD) },
                  ({ getFieldValue }) => ({
                    validator(_, value) {
                      if (!value || getFieldValue("password") === value) {
                        return Promise.resolve();
                      }
                      return Promise.reject(
                        new Error(t(K.TWO_PASSWORDS_ARE_INCONSISTENT))
                      );
                    },
                  }),
                ]}
              >
                <Input
                  prefix={<LockOutlined className={styles.inputPrefixIcon} />}
                  type="password"
                  placeholder={t(K.PASSWORD_CONFIRM)}
                />
              </Form.Item>
              {!hideInvite &&
                (isCommonSignup ? (
                  <>
                    <Form.Item
                      validateFirst={true}
                      rules={[
                        {
                          required: true,
                          message: t(K.PLEASE_FILL_IN_VALID_PHONE_NUMBER),
                        },
                        {
                          validator: (_, value) => {
                            if (
                              /^(?=\d{11}$)^1(?:3\d|4[57]|5[^4\D]|7[^249\D]|8\d)\d{8}$/.test(
                                value
                              )
                            ) {
                              setVerifyBtnDisabled(false);
                              return Promise.resolve();
                            }
                            setVerifyBtnDisabled(true);
                            return Promise.reject(
                              new Error(t(K.PLEASE_FILL_IN_VALID_PHONE_NUMBER))
                            );
                          },
                        },
                      ]}
                      name="phone"
                    >
                      <Input
                        prefix={
                          <PhoneOutlined
                            className={styles.inputPrefixIcon}
                            rotate={90}
                          />
                        }
                        suffix={
                          <Button
                            disabled={verifyBtnDisabled}
                            type="text"
                            onClick={handleVerifyBtnClick}
                            id="verifyBtn"
                          >
                            {content}
                          </Button>
                        }
                        placeholder={t(K.PHONE)}
                      />
                    </Form.Item>
                    <Form.Item
                      rules={[
                        {
                          required: true,
                          message: t(K.PLEASE_INPUT_PHRASE),
                        },
                        {
                          pattern: /^\d{6}$/,
                          message: t(K.PLEASE_INPUT_VALID_PHRASE),
                        },
                      ]}
                      name="verification_code"
                    >
                      <Input
                        prefix={
                          <SafetyOutlined className={styles.inputPrefixIcon} />
                        }
                        placeholder={t(K.VERIFY_CODE)}
                      ></Input>
                    </Form.Item>
                  </>
                ) : (
                  <Form.Item
                    validateFirst={true}
                    name="invite"
                    rules={[
                      {
                        required: true,
                        message: t([K.PLEASE_FILL_IN_INVITE_CODE]),
                      },
                      {
                        pattern: iniviteCodePattern,
                        message: t([K.PLEASE_FILL_IN_INVITE_CODE]),
                      },
                    ]}
                  >
                    <Input
                      prefix={
                        <GeneralIcon
                          icon={{
                            lib: "easyops",
                            icon: "release-management",
                            category: "menu",
                            color: "rgba(0,0,0,.25)",
                          }}
                        />
                      }
                      type="text"
                      placeholder={t(K.INVITE_CODE)}
                    />
                  </Form.Item>
                ))}
              <Form.Item
                name="terms"
                valuePropName="checked"
                rules={[
                  {
                    validator: (_, value) =>
                      value
                        ? Promise.resolve()
                        : Promise.reject(new Error(t(K.AGREE_TERMS_TIPS))),
                  },
                ]}
              >
                <Checkbox>
                  {t(K.AGREE_TERMS)}
                  <a
                    onClick={() => {
                      showTerms();
                    }}
                    id="TermsLink"
                  >
                    {t(K.UWINTECH_TERMS)}
                  </a>
                </Checkbox>
              </Form.Item>
              <Form.Item>
                <Button
                  type="primary"
                  htmlType="submit"
                  style={{
                    width: "100%",
                    height: 34,
                  }}
                  id="submitBtn"
                >
                  {t(K.REGISTER)}
                </Button>
              </Form.Item>
              <Form.Item>
                <div style={{ textAlign: "center" }}>
                  {t(K.ALREADY_HAVE_AN_ACCOUNT)}
                  <a
                    id="LogInLink"
                    onClick={() => {
                      getHistory().push(
                        createLocation({
                          pathname: props.loginUrl ?? "/auth/login",
                        })
                      );
                    }}
                  >
                    {t(K.LOGIN_IMMEDIATELY)}
                  </a>
                </div>
              </Form.Item>
            </Form>
          </Card>
          <Modal
            visible={isTermsVisible}
            title={t(K.UWINTECH_TERMS)}
            width={598}
            okType="default"
            cancelText={t(K.DISAGREE)}
            okText={t(K.AGREE)}
            closable={false}
            onCancel={() => {
              disagreeTerms();
            }}
            onOk={() => {
              agreeTerms();
            }}
          >
            <Terms />
          </Modal>
        </div>
      </div>
    </>
  );
}
Example #22
Source File: SchemaItem.tsx    From next-basics with GNU General Public License v3.0 4 votes vote down vote up
export function SchemaItem({
  style,
  readonly,
  className,
  itemData,
  traceId,
  hideDeleteBtn,
  hiddenRootNode,
  disabledModelType,
  isModelDefinitionRow,
  parentsModel = [],
}: SchemaItemProps): React.ReactElement {
  const { t } = useTranslation(NS_FLOW_BUILDER);
  const editorContext = useContext(EditorContext);
  const [hover, setHover] = useState(false);
  const [expand, setExpand] = useState(false);
  const {
    modelDefinitionList = [],
    onModal,
    onRemove,
    showModelDefinition,
    hideModelDefinition,
  } = editorContext;

  useEffect(() => {
    setExpand(!isEmpty(itemData.fields));
  }, [itemData.fields]);

  const openEditModal = (): void => {
    onModal?.({ ...itemData }, true, traceId);
  };

  const openCreateModal = (): void => {
    onModal?.({} as SchemaItemProperty, false, traceId);
  };

  const displayName = useMemo(
    () => itemData.name || itemData.ref,
    [itemData.name, itemData.ref]
  );

  const offsetPadding = useMemo(() => {
    return 20 * (traceId.split("-").length - 1);
  }, [traceId]);

  const modelDefinition = useMemo(() => {
    return modelDefinitionList.find(
      (item) => item.name === calcModelDefinition(itemData)
    );
  }, [modelDefinitionList, itemData]);

  const isSelfRef = useMemo(
    () =>
      isModelDefinition(itemData) &&
      parentsModel.includes(calcModelDefinition(itemData)),
    [itemData, parentsModel]
  );

  const handleExpand = (): void => {
    setExpand(true);
    if (itemData.ref) {
      showModelDefinition(
        getModelRefData(itemData.ref, modelDefinition, modelDefinitionList),
        traceId
      );
    } else {
      showModelDefinition(modelDefinition, traceId);
    }
  };

  const handleFold = (): void => {
    setExpand(false);
    hideModelDefinition(traceId);
  };

  const handleClick = (): void => {
    expand ? handleFold() : handleExpand();
  };

  return (
    <div
      className={classNames({ [styles.highlight]: modelDefinition && expand })}
    >
      <div
        style={style}
        className={className}
        hidden={traceId === rootTraceId && hiddenRootNode}
      >
        <div
          title={displayName}
          className={classNames(styles.textEllipsis, {
            [styles.modelDefinitionText]: isModelDefinitionRow,
          })}
          style={{
            paddingLeft: offsetPadding,
            ...(hover ? { color: "var(--color-brand)" } : {}),
          }}
        >
          {modelDefinition && !isSelfRef ? (
            <span onClick={handleClick} style={{ cursor: "pointer" }}>
              {expand ? (
                <DownOutlined className={styles.caret} />
              ) : (
                <RightOutlined className={styles.caret} />
              )}
              {displayName}
            </span>
          ) : (
            displayName
          )}
        </div>
        <div>
          <Checkbox checked={itemData.required} disabled />
        </div>
        <div className={styles.type}>
          <Tooltip
            title={
              itemData.type ? t(K.SCHEMA_ITEM_NORMAL) : t(K.SCHEMA_ITEM_REF)
            }
          >
            <Tag
              className={classNames({
                [styles.typeTag]: itemData.type,
                [styles.refTag]: itemData.ref,
                [styles.modelDefinitionTag]: isModelDefinitionRow,
              })}
            >
              {itemData.type || itemData.ref}
            </Tag>
          </Tooltip>
          {!readonly && modelDefinition?.updated && (
            <Tooltip title={t(K.MODEL_DEFINITION_UPDATE_MESSAGE)}>
              <Badge color="orange" />
            </Tooltip>
          )}
        </div>
        <div
          className={classNames(styles.textEllipsis, {
            [styles.modelDefinitionText]: isModelDefinitionRow,
          })}
          title={itemData.description}
        >
          {itemData.description}
        </div>
        {!readonly && (
          <div hidden={isModelDefinitionRow}>
            <Button
              type="link"
              className={editorStyles.iconBtn}
              style={{ marginRight: 8 }}
              onClick={openEditModal}
            >
              <SettingOutlined />
            </Button>
            {!hideDeleteBtn && (
              <Button
                type="link"
                className={editorStyles.deleteBtn}
                onClick={() => onRemove?.(traceId)}
              >
                <DeleteOutlined />
              </Button>
            )}
          </div>
        )}
      </div>
      {itemData.fields?.map((item, index) => (
        <SchemaItem
          parentsModel={
            isModelDefinition(itemData)
              ? [...parentsModel, calcModelDefinition(itemData)]
              : [...parentsModel]
          }
          className={editorStyles.schemaItem}
          isModelDefinitionRow={isModelDefinitionRow || !!modelDefinition}
          readonly={readonly}
          style={{
            gridTemplateColumns: getGridTemplateColumns(
              filterTitleList(titleList, readonly)
            ),
          }}
          key={index}
          traceId={`${traceId}-${index}`}
          itemData={item}
          disabledModelType={disabledModelType}
        />
      ))}
      {!readonly && allowExpandFields(itemData.type) && (
        <div
          style={{ paddingLeft: 20 + offsetPadding }}
          hidden={isModelDefinitionRow}
        >
          <Button
            className={editorStyles.iconBtn}
            type="link"
            title={t(K.ADD_FIELD_PARAMS_TIPS, { name: displayName })}
            onMouseEnter={() => setHover(true)}
            onMouseLeave={() => setHover(false)}
            onClick={openCreateModal}
          >
            <PlusCircleOutlined />
            {t(K.FIELD_PARAMS)}
          </Button>
        </div>
      )}
    </div>
  );
}
Example #23
Source File: DesktopSlider.tsx    From next-basics with GNU General Public License v3.0 4 votes vote down vote up
export function DesktopSlider(props: DesktopSliderProps): React.ReactElement {
  const enableMyDesktop = true;
  const [desktopCursor, setDesktopCursor] = React.useState(
    getRememberedDesktopCursor()
  );
  const [appCursor, setAppCursor] = React.useState(-1);
  const { columns, rows } = useLaunchpadSettingsContext();
  const { setDesktopDir } = useDesktopDirContext();
  const slideDuration = 400;

  useEffect(() => {
    enableMyDesktop && launchpadService.setMaxVisitorLength(8);
  }, [columns, enableMyDesktop]);
  const mapItemToDesktop = (apps: appItem[]): DesktopData => ({
    name: "-",
    items: apps.map((app) => {
      if (app.type === "custom") {
        return {
          id: app.id,
          name: app.name,
          url: app.homepage,
          type: "custom",
          app,
        };
      }
      return {
        type: "app",
        id: app.id,
        app,
      };
    }),
  });

  const transformCustomItem = (item: DesktopItemCustom): appItem => ({
    id: item.id,
    localeName: item.name,
    name: item.name,
    homepage: item.url,
    ...item,
    type: "custom",
  });

  let desktops: DesktopData[];
  let validItems = props.microApps;

  if (props.desktops && props.desktops.length > 0) {
    validItems = [];

    const id2app = props.microApps.reduce((acc, app) => {
      acc.set(app.id, app);
      return acc;
    }, new Map<string, appItem>());

    desktops = props.desktops
      .map((desktop) => ({
        name: desktop.name,
        items: desktop.items
          .map((item) => {
            if (item.type === "app") {
              if (id2app.has(item.id)) {
                const app = id2app.get(item.id);
                validItems.push(app);
                id2app.delete(item.id);
                return {
                  type: item.type,
                  id: item.id,
                  app,
                };
              }
              // ignore not found apps
            } else if (item.type === "dir") {
              const items = item.items
                .map((item) => {
                  if (item.type === "app") {
                    if (id2app.has(item.id)) {
                      const app = id2app.get(item.id);
                      validItems.push(app);
                      id2app.delete(item.id);
                      return {
                        type: item.type,
                        id: item.id,
                        app,
                      };
                    }
                  } else if (item.type === "custom") {
                    validItems.push(transformCustomItem(item));
                    return item;
                  }
                })
                .filter(Boolean);
              // ignore empty dirs
              if (items.length > 0) {
                return {
                  type: item.type,
                  id: item.id,
                  name: item.name,
                  items,
                };
              }
            } else if (item.type === "custom") {
              validItems.push(transformCustomItem(item));
              return item;
            }
          })
          .filter(Boolean)
          .slice(0, columns * rows),
      }))
      // ignore empty desktops
      .filter((desktop) => desktop.items.length > 0);
  } else {
    // 如果没有定义桌面列表(例如本地开发模式),则自动按数量切割。
    desktops = chunk(props.microApps, columns * rows).map(mapItemToDesktop);
  }
  let filteredDesktop: DesktopData;
  if (props.q) {
    const lowerQ = props.q.toLowerCase();
    filteredDesktop = mapItemToDesktop(
      validItems
        .filter(
          (app) =>
            app.localeName.toLowerCase().includes(lowerQ) ||
            app.name.toLowerCase().includes(lowerQ) ||
            app.id.toLowerCase().includes(lowerQ)
        )
        .slice(0, columns * rows)
    );
  }

  // When sliding desktop, reset app cursor.
  React.useEffect(() => {
    setAppCursor(-1);
  }, [desktopCursor]);

  // When making search, set app cursor to the first app.
  React.useEffect(() => {
    setAppCursor(
      props.q && filteredDesktop && filteredDesktop.items.length > 0 ? 0 : -1
    );
  }, [props.q]);

  const lockRef = React.useRef(false);

  const throttledSetDesktopCursor = (index: number): void => {
    if (lockRef.current) {
      return;
    }
    setRememberedDesktopCursor(index);
    setDesktopCursor(index);
    // 一次滑动一个屏幕,锁定期间内,不能继续滑动屏幕。
    lockRef.current = true;
    setTimeout(() => {
      lockRef.current = false;
    }, slideDuration);
  };

  const slideLeft = React.useCallback((): void => {
    if (desktopCursor > 0) {
      throttledSetDesktopCursor(desktopCursor - 1);
    }
  }, [desktopCursor]);

  const slideRight = React.useCallback((): void => {
    const length = desktops.length;
    if (desktopCursor < length) {
      throttledSetDesktopCursor(desktopCursor + 1);
    }
  }, [desktopCursor, desktops.length]);

  const handleSlideLeft = (e: React.MouseEvent): void => {
    e.stopPropagation();
    slideLeft();
  };

  const handleSlideRight = (e: React.MouseEvent): void => {
    e.stopPropagation();
    slideRight();
  };

  const handleSlideTo = (e: React.MouseEvent, index: number): void => {
    e.stopPropagation();
    if (desktopCursor !== index) {
      throttledSetDesktopCursor(index);
    }
  };

  // Press arrow key to select an app.
  React.useEffect(() => {
    const onKeydown = (event: KeyboardEvent): void => {
      // 第一栏为我的面板,须过滤掉(PS: 但是搜索时 desktopCursor 为0是可以的)
      if (enableMyDesktop && desktopCursor === 0 && !props.q) return;
      const key =
        event.key ||
        /* istanbul ignore next: compatibility */ event.keyCode ||
        /* istanbul ignore next: compatibility */ event.which;
      const currentDesktop = props.q
        ? filteredDesktop
        : desktops[desktopCursor - 1];
      if (key === "Enter" || key === 13) {
        event.preventDefault();
        if (appCursor >= 0 && appCursor < currentDesktop.items.length) {
          const cell = currentDesktop.items[appCursor];
          if (cell.type === "app") {
            launchpadService.pushVisitor("app", cell.app);
            getRuntime().resetWorkspaceStack();
            getHistory().push(cell.app.homepage);
          } else if (cell.type === "custom") {
            launchpadService.pushVisitor("custom", cell);
            window.open(cell.url);
          } else if (cell.type === "dir") {
            // Calculate the approximate coordinates of a dir.
            const x = appCursor % columns;
            const y = Math.floor(appCursor / columns);
            setDesktopDir({
              activeIndex: 0,
              dir: {
                name: cell.name,
                items: cell.items,
              },
              coordinates: {
                x: (window.innerWidth * (x + 1)) / (columns + 1),
                y: (window.innerHeight * (y + 1)) / (rows + 1),
              },
            });
          }
        }
      } else {
        let offset = 0;
        if (key === "ArrowRight" || key === 39) {
          offset = 1;
        } else if (key === "ArrowLeft" || key === 37) {
          offset = appCursor === -1 ? currentDesktop.items.length : -1;
        } else if (key === "ArrowDown" || key === 40) {
          offset = appCursor === -1 ? 1 : columns;
        } else if (key === "ArrowUp" || key === 38) {
          offset = appCursor === -1 ? currentDesktop.items.length : -columns;
        }
        if (offset !== 0) {
          event.preventDefault();
          const next = appCursor + offset;
          if (next >= 0 && next < currentDesktop.items.length) {
            setAppCursor(next);
          }
        }
      }
    };
    window.addEventListener("keydown", onKeydown);
    return () => {
      window.removeEventListener("keydown", onKeydown);
    };
  }, [desktopCursor, appCursor, props.q, columns, setDesktopDir]);

  const deltaXRef = React.useRef(0);
  const deltaYRef = React.useRef(0);
  const responsibleRef = React.useRef(true);

  const tryToSlideByWheel = (): void => {
    // Mac 的 trackpad,部分鼠标滚轮会有“拖尾效应”,拖尾期间,不再响应滚轮事件。
    if (!responsibleRef.current) {
      return;
    }
    // 取绝对值较大的方向
    const axisRef =
      Math.abs(deltaYRef.current) > Math.abs(deltaXRef.current)
        ? deltaYRef
        : deltaXRef;
    // 经测试,滚轮纵轴一次位移 4,横轴一次位移 40。
    const threshold = axisRef === deltaYRef ? 4 : 40;
    if (axisRef.current >= threshold) {
      slideRight();
    } else if (axisRef.current <= -threshold) {
      slideLeft();
    } else {
      return;
    }
    // 触发滑动后,重设 delta,拖尾期间,不再响应滚轮事件。
    deltaXRef.current = 0;
    deltaYRef.current = 0;
    responsibleRef.current = false;
  };

  const resetDeltaTimeoutRef = React.useRef<any>();

  const handleWheel = (e: React.WheelEvent): void => {
    deltaXRef.current += e.deltaX;
    deltaYRef.current += e.deltaY;
    tryToSlideByWheel();
    if (resetDeltaTimeoutRef.current) {
      clearTimeout(resetDeltaTimeoutRef.current);
    }
    // 间隔 50ms 内的连续滚轮事件被认作一次滚动。
    resetDeltaTimeoutRef.current = setTimeout(() => {
      deltaXRef.current = 0;
      deltaYRef.current = 0;
      responsibleRef.current = true;
    }, 50);
  };

  const sliderChildrenLength = desktops.length + 1;

  return (
    <div
      onWheel={handleWheel}
      className={classNames(styles.desktopSlider, {
        [styles.filtered]: props.q,
      })}
    >
      <div className={styles.desktopSelector}>
        {[...[{ name: <HomeFilled /> }], ...desktops].map((desktop, index) => (
          <React.Fragment key={index}>
            {index !== 0 && <span className={styles.selectorSeparator} />}
            <a
              className={classNames(styles.desktopName, {
                [styles.active]: desktopCursor === index,
              })}
              onClick={(e) => handleSlideTo(e, index)}
              role="button"
            >
              {desktop.name}
            </a>
          </React.Fragment>
        ))}
      </div>
      <div className={styles.scrollContainer}>
        <div
          className={styles.desktopList}
          style={{
            width: `${sliderChildrenLength * 100}%`,
            marginLeft: `${desktopCursor * -100}%`,
            transition: `margin-left ${slideDuration}ms ease-out`,
          }}
        >
          {enableMyDesktop && (
            <MyDesktop
              desktopCount={desktops.length}
              arrowWidthPercent={props.arrowWidthPercent}
            />
          )}
          {desktops.map((desktop, index) => (
            <Desktop
              key={index}
              desktop={desktop}
              desktopCount={desktops.length}
              arrowWidthPercent={props.arrowWidthPercent}
              activeIndex={desktopCursor - 1 === index ? appCursor : -1}
            />
          ))}
        </div>
        {
          // Show filtered apps as a single desktop.
          props.q && (
            <div className={styles.filteredList}>
              <Desktop
                desktop={filteredDesktop}
                desktopCount={1}
                arrowWidthPercent={props.arrowWidthPercent}
                activeIndex={appCursor}
              />
            </div>
          )
        }
      </div>
      <a
        className={classNames(styles.arrowLeft, {
          [styles.available]: desktopCursor > 0,
        })}
        style={{ width: `${props.arrowWidthPercent}%` }}
        onClick={handleSlideLeft}
        role="button"
      >
        <span className={styles.arrowButton}>
          <LeftOutlined />
        </span>
      </a>
      <a
        className={classNames(styles.arrowRight, {
          [styles.available]: desktopCursor < sliderChildrenLength - 1,
        })}
        style={{ width: `${props.arrowWidthPercent}%` }}
        onClick={handleSlideRight}
        role="button"
      >
        <span className={styles.arrowButton}>
          <RightOutlined />
        </span>
      </a>
    </div>
  );
}
Example #24
Source File: DendronTreeMenu.tsx    From dendron with GNU Affero General Public License v3.0 4 votes vote down vote up
function MenuView({
  roots,
  expandKeys,
  onSelect,
  onExpand,
  collapsed,
  activeNote,
  noteIndex,
}: {
  roots: DataNode[];
  expandKeys: string[];
  onSelect: (noteId: string) => void;
  onExpand: (noteId: string) => void;
  collapsed: boolean;
  activeNote: string | undefined;
} & Partial<NoteData>) {
  const ExpandIcon = useCallback(
    ({ isOpen, ...rest }: { isOpen: boolean }) => {
      const UncollapsedIcon = isOpen ? UpOutlined : DownOutlined;
      const Icon = collapsed ? RightOutlined : UncollapsedIcon;
      return (
        <i data-expandedicon="true">
          <Icon
            style={{
              pointerEvents: "none", // only allow custom element to be gesture target
              margin: 0,
            }}
          />
        </i>
      );
    },
    [collapsed]
  );

  const createMenu = (menu: DataNode) => {
    if (menu.children && menu.children.length > 0) {
      return (
        <SubMenu
          icon={menu.icon}
          className={
            menu.key === activeNote ? "dendron-ant-menu-submenu-selected" : ""
          }
          key={menu.key}
          title={<MenuItemTitle menu={menu} noteIndex={noteIndex!} />}
          onTitleClick={(event) => {
            const target = event.domEvent.target as HTMLElement;
            const isArrow = target.dataset.expandedicon;
            if (!isArrow) {
              onSelect(event.key);
            } else {
              onExpand(event.key);
            }
          }}
        >
          {menu.children.map((childMenu: DataNode) => {
            return createMenu(childMenu);
          })}
        </SubMenu>
      );
    }
    return (
      <MenuItem key={menu.key} icon={menu.icon}>
        <MenuItemTitle menu={menu} noteIndex={noteIndex!} />
      </MenuItem>
    );
  };

  if (activeNote) {
    expandKeys.push(activeNote);
  }

  return (
    <Menu
      key={String(collapsed)}
      className="dendron-tree-menu"
      mode="inline"
      {...(!collapsed && {
        openKeys: expandKeys,
        selectedKeys: expandKeys,
      })}
      inlineIndent={DENDRON_STYLE_CONSTANTS.SIDER.INDENT}
      expandIcon={ExpandIcon}
      inlineCollapsed={collapsed}
      // results in gray box otherwise when nav bar is too short for display
      style={{ height: "100%" }}
    >
      {roots.map((menu) => {
        return createMenu(menu);
      })}
    </Menu>
  );
}
Example #25
Source File: Icon.tsx    From html2sketch with MIT License 4 votes vote down vote up
IconSymbol: FC = () => {
  return (
    <Row>
      {/*<CaretUpOutlined*/}
      {/*  className="icon"*/}
      {/*  symbolName={'1.General/2.Icons/1.CaretUpOutlined'}*/}
      {/*/>*/}
      {/*  className="icon"*/}
      {/*  symbolName={'1.General/2.Icons/2.MailOutlined'}*/}
      {/*/>*/}
      {/*<StepBackwardOutlined*/}
      {/*  className="icon"*/}
      {/*  symbolName={'1.General/2.Icons/2.StepBackwardOutlined'}*/}
      {/*/>*/}
      {/*<StepForwardOutlined*/}
      {/*  className="icon"*/}
      {/*  symbolName={'1.General/2.Icons/2.StepBackwardOutlined'}*/}
      {/*/>*/}
      <StepForwardOutlined />
      <ShrinkOutlined />
      <ArrowsAltOutlined />
      <DownOutlined />
      <UpOutlined />
      <LeftOutlined />
      <RightOutlined />
      <CaretUpOutlined />
      <CaretDownOutlined />
      <CaretLeftOutlined />
      <CaretRightOutlined />
      <VerticalAlignTopOutlined />
      <RollbackOutlined />
      <FastBackwardOutlined />
      <FastForwardOutlined />
      <DoubleRightOutlined />
      <DoubleLeftOutlined />
      <VerticalLeftOutlined />
      <VerticalRightOutlined />
      <VerticalAlignMiddleOutlined />
      <VerticalAlignBottomOutlined />
      <ForwardOutlined />
      <BackwardOutlined />
      <EnterOutlined />
      <RetweetOutlined />
      <SwapOutlined />
      <SwapLeftOutlined />
      <SwapRightOutlined />
      <ArrowUpOutlined />
      <ArrowDownOutlined />
      <ArrowLeftOutlined />
      <ArrowRightOutlined />
      <LoginOutlined />
      <LogoutOutlined />
      <MenuFoldOutlined />
      <MenuUnfoldOutlined />
      <BorderBottomOutlined />
      <BorderHorizontalOutlined />
      <BorderInnerOutlined />
      <BorderOuterOutlined />
      <BorderLeftOutlined />
      <BorderRightOutlined />
      <BorderTopOutlined />
      <BorderVerticleOutlined />
      <PicCenterOutlined />
      <PicLeftOutlined />
      <PicRightOutlined />
      <RadiusBottomleftOutlined />
      <RadiusBottomrightOutlined />
      <RadiusUpleftOutlined />
      <RadiusUprightOutlined />
      <FullscreenOutlined />
      <FullscreenExitOutlined />
      <QuestionOutlined />
      <PauseOutlined />
      <MinusOutlined />
      <PauseCircleOutlined />
      <InfoOutlined />
      <CloseOutlined />
      <ExclamationOutlined />
      <CheckOutlined />
      <WarningOutlined />
      <IssuesCloseOutlined />
      <StopOutlined />
      <EditOutlined />
      <CopyOutlined />
      <ScissorOutlined />
      <DeleteOutlined />
      <SnippetsOutlined />
      <DiffOutlined />
      <HighlightOutlined />
      <AlignCenterOutlined />
      <AlignLeftOutlined />
      <AlignRightOutlined />
      <BgColorsOutlined />
      <BoldOutlined />
      <ItalicOutlined />
      <UnderlineOutlined />
      <StrikethroughOutlined />
      <RedoOutlined />
      <UndoOutlined />
      <ZoomInOutlined />
      <ZoomOutOutlined />
      <FontColorsOutlined />
      <FontSizeOutlined />
      <LineHeightOutlined />
      <SortAscendingOutlined />
      <SortDescendingOutlined />
      <DragOutlined />
      <OrderedListOutlined />
      <UnorderedListOutlined />
      <RadiusSettingOutlined />
      <ColumnWidthOutlined />
      <ColumnHeightOutlined />
      <AreaChartOutlined />
      <PieChartOutlined />
      <BarChartOutlined />
      <DotChartOutlined />
      <LineChartOutlined />
      <RadarChartOutlined />
      <HeatMapOutlined />
      <FallOutlined />
      <RiseOutlined />
      <StockOutlined />
      <BoxPlotOutlined />
      <FundOutlined />
      <SlidersOutlined />
      <AndroidOutlined />
      <AppleOutlined />
      <WindowsOutlined />
      <IeOutlined />
      <ChromeOutlined />
      <GithubOutlined />
      <AliwangwangOutlined />
      <DingdingOutlined />
      <WeiboSquareOutlined />
      <WeiboCircleOutlined />
      <TaobaoCircleOutlined />
      <Html5Outlined />
      <WeiboOutlined />
      <TwitterOutlined />
      <WechatOutlined />
      <AlipayCircleOutlined />
      <TaobaoOutlined />
      <SkypeOutlined />
      <FacebookOutlined />
      <CodepenOutlined />
      <CodeSandboxOutlined />
      <AmazonOutlined />
      <GoogleOutlined />
      <AlipayOutlined />
      <AntDesignOutlined />
      <AntCloudOutlined />
      <ZhihuOutlined />
      <SlackOutlined />
      <SlackSquareOutlined />
      <BehanceSquareOutlined />
      <DribbbleOutlined />
      <DribbbleSquareOutlined />
      <InstagramOutlined />
      <YuqueOutlined />
      <AlibabaOutlined />
      <YahooOutlined />
      <RedditOutlined />
      <SketchOutlined />
      <AccountBookOutlined />
      <AlertOutlined />
      <ApartmentOutlined />
      <ApiOutlined />
      <QqOutlined />
      <MediumWorkmarkOutlined />
      <GitlabOutlined />
      <MediumOutlined />
      <GooglePlusOutlined />
      <AppstoreAddOutlined />
      <AppstoreOutlined />
      <AudioOutlined />
      <AudioMutedOutlined />
      <AuditOutlined />
      <BankOutlined />
      <BarcodeOutlined />
      <BarsOutlined />
      <BellOutlined />
      <BlockOutlined />
      <BookOutlined />
      <BorderOutlined />
      <BranchesOutlined />
      <BuildOutlined />
      <BulbOutlined />
      <CalculatorOutlined />
      <CalendarOutlined />
      <CameraOutlined />
      <CarOutlined />
      <CarryOutOutlined />
      <CiCircleOutlined />
      <CiOutlined />
      <CloudOutlined />
      <ClearOutlined />
      <ClusterOutlined />
      <CodeOutlined />
      <CoffeeOutlined />
      <CompassOutlined />
      <CompressOutlined />
      <ContactsOutlined />
      <ContainerOutlined />
      <ControlOutlined />
      <CopyrightCircleOutlined />
      <CopyrightOutlined />
      <CreditCardOutlined />
      <CrownOutlined />
      <CustomerServiceOutlined />
      <DashboardOutlined />
      <DatabaseOutlined />
      <DeleteColumnOutlined />
      <DeleteRowOutlined />
      <DisconnectOutlined />
      <DislikeOutlined />
      <DollarCircleOutlined />
      <DollarOutlined />
      <DownloadOutlined />
      <EllipsisOutlined />
      <EnvironmentOutlined />
      <EuroCircleOutlined />
      <EuroOutlined />
      <ExceptionOutlined />
      <ExpandAltOutlined />
      <ExpandOutlined />
      <ExperimentOutlined />
      <ExportOutlined />
      <EyeOutlined />
      <FieldBinaryOutlined />
      <FieldNumberOutlined />
      <FieldStringOutlined />
      <DesktopOutlined />
      <DingtalkOutlined />
      <FileAddOutlined />
      <FileDoneOutlined />
      <FileExcelOutlined />
      <FileExclamationOutlined />
      <FileOutlined />
      <FileImageOutlined />
      <FileJpgOutlined />
      <FileMarkdownOutlined />
      <FilePdfOutlined />
      <FilePptOutlined />
      <FileProtectOutlined />
      <FileSearchOutlined />
      <FileSyncOutlined />
      <FileTextOutlined />
      <FileUnknownOutlined />
      <FileWordOutlined />
      <FilterOutlined />
      <FireOutlined />
      <FlagOutlined />
      <FolderAddOutlined />
      <FolderOutlined />
      <FolderOpenOutlined />
      <ForkOutlined />
      <FormatPainterOutlined />
      <FrownOutlined />
      <FunctionOutlined />
      <FunnelPlotOutlined />
      <GatewayOutlined />
      <GifOutlined />
      <GiftOutlined />
      <GlobalOutlined />
      <GoldOutlined />
      <GroupOutlined />
      <HddOutlined />
      <HeartOutlined />
      <HistoryOutlined />
      <HomeOutlined />
      <HourglassOutlined />
      <IdcardOutlined />
      <ImportOutlined />
      <InboxOutlined />
      <InsertRowAboveOutlined />
      <InsertRowBelowOutlined />
      <InsertRowLeftOutlined />
      <InsertRowRightOutlined />
      <InsuranceOutlined />
      <InteractionOutlined />
      <KeyOutlined />
      <LaptopOutlined />
      <LayoutOutlined />
      <LikeOutlined />
      <LineOutlined />
      <LinkOutlined />
      <Loading3QuartersOutlined />
      <LoadingOutlined />
      <LockOutlined />
      <MailOutlined />
      <ManOutlined />
      <MedicineBoxOutlined />
      <MehOutlined />
      <MenuOutlined />
      <MergeCellsOutlined />
      <MessageOutlined />
      <MobileOutlined />
      <MoneyCollectOutlined />
      <MonitorOutlined />
      <MoreOutlined />
      <NodeCollapseOutlined />
      <NodeExpandOutlined />
      <NodeIndexOutlined />
      <NotificationOutlined />
      <NumberOutlined />
      <PaperClipOutlined />
      <PartitionOutlined />
      <PayCircleOutlined />
      <PercentageOutlined />
      <PhoneOutlined />
      <PictureOutlined />
      <PoundCircleOutlined />
      <PoundOutlined />
      <PoweroffOutlined />
      <PrinterOutlined />
      <ProfileOutlined />
      <ProjectOutlined />
      <PropertySafetyOutlined />
      <PullRequestOutlined />
      <PushpinOutlined />
      <QrcodeOutlined />
      <ReadOutlined />
      <ReconciliationOutlined />
      <RedEnvelopeOutlined />
      <ReloadOutlined />
      <RestOutlined />
      <RobotOutlined />
      <RocketOutlined />
      <SafetyCertificateOutlined />
      <SafetyOutlined />
      <ScanOutlined />
      <ScheduleOutlined />
      <SearchOutlined />
      <SecurityScanOutlined />
      <SelectOutlined />
      <SendOutlined />
      <SettingOutlined />
      <ShakeOutlined />
      <ShareAltOutlined />
      <ShopOutlined />
      <ShoppingCartOutlined />
      <ShoppingOutlined />
      <SisternodeOutlined />
      <SkinOutlined />
      <SmileOutlined />
      <SolutionOutlined />
      <SoundOutlined />
      <SplitCellsOutlined />
      <StarOutlined />
      <SubnodeOutlined />
      <SyncOutlined />
      <TableOutlined />
      <TabletOutlined />
      <TagOutlined />
      <TagsOutlined />
      <TeamOutlined />
      <ThunderboltOutlined />
      <ToTopOutlined />
      <ToolOutlined />
      <TrademarkCircleOutlined />
      <TrademarkOutlined />
      <TransactionOutlined />
      <TrophyOutlined />
      <UngroupOutlined />
      <UnlockOutlined />
      <UploadOutlined />
      <UsbOutlined />
      <UserAddOutlined />
      <UserDeleteOutlined />
      <UserOutlined />
      <UserSwitchOutlined />
      <UsergroupAddOutlined />
      <UsergroupDeleteOutlined />
      <VideoCameraOutlined />
      <WalletOutlined />
      <WifiOutlined />
      <BorderlessTableOutlined />
      <WomanOutlined />
      <BehanceOutlined />
      <DropboxOutlined />
      <DeploymentUnitOutlined />
      <UpCircleOutlined />
      <DownCircleOutlined />
      <LeftCircleOutlined />
      <RightCircleOutlined />
      <UpSquareOutlined />
      <DownSquareOutlined />
      <LeftSquareOutlined />
      <RightSquareOutlined />
      <PlayCircleOutlined />
      <QuestionCircleOutlined />
      <PlusCircleOutlined />
      <PlusSquareOutlined />
      <MinusSquareOutlined />
      <MinusCircleOutlined />
      <InfoCircleOutlined />
      <ExclamationCircleOutlined />
      <CloseCircleOutlined />
      <CloseSquareOutlined />
      <CheckCircleOutlined />
      <CheckSquareOutlined />
      <ClockCircleOutlined />
      <FormOutlined />
      <DashOutlined />
      <SmallDashOutlined />
      <YoutubeOutlined />
      <CodepenCircleOutlined />
      <AliyunOutlined />
      <PlusOutlined />
      <LinkedinOutlined />
      <AimOutlined />
      <BugOutlined />
      <CloudDownloadOutlined />
      <CloudServerOutlined />
      <CloudSyncOutlined />
      <CloudUploadOutlined />
      <CommentOutlined />
      <ConsoleSqlOutlined />
      <EyeInvisibleOutlined />
      <FileGifOutlined />
      <DeliveredProcedureOutlined />
      <FieldTimeOutlined />
      <FileZipOutlined />
      <FolderViewOutlined />
      <FundProjectionScreenOutlined />
      <FundViewOutlined />
      <MacCommandOutlined />
      <PlaySquareOutlined />
      <OneToOneOutlined />
      <RotateLeftOutlined />
      <RotateRightOutlined />
      <SaveOutlined />
      <SwitcherOutlined />
      <TranslationOutlined />
      <VerifiedOutlined />
      <VideoCameraAddOutlined />
      <WhatsAppOutlined />

      {/*</Col>*/}
    </Row>
  );
}
Example #26
Source File: SessionRecordingsTable.tsx    From posthog-foss with MIT License 4 votes vote down vote up
export function SessionRecordingsTable({ personUUID, isPersonPage = false }: SessionRecordingsTableProps): JSX.Element {
    const sessionRecordingsTableLogicInstance = sessionRecordingsTableLogic({ personUUID })
    const {
        sessionRecordings,
        sessionRecordingsResponseLoading,
        sessionRecordingId,
        entityFilters,
        propertyFilters,
        hasNext,
        hasPrev,
        fromDate,
        toDate,
        durationFilter,
        showFilters,
    } = useValues(sessionRecordingsTableLogicInstance)
    const {
        openSessionPlayer,
        closeSessionPlayer,
        setEntityFilters,
        setPropertyFilters,
        loadNext,
        loadPrev,
        setDateRange,
        setDurationFilter,
        enableFilter,
    } = useActions(sessionRecordingsTableLogicInstance)
    const { preflight } = useValues(preflightLogic)

    const columns: LemonTableColumns<SessionRecordingType> = [
        {
            title: 'Start time',
            render: function RenderStartTime(_: any, sessionRecording: SessionRecordingType) {
                return <TZLabel time={sessionRecording.start_time} formatString="MMMM DD, YYYY h:mm" />
            },
        },
        {
            title: 'Duration',
            render: function RenderDuration(_: any, sessionRecording: SessionRecordingType) {
                return <span>{humanFriendlyDuration(sessionRecording.recording_duration)}</span>
            },
        },
        {
            title: 'Person',
            key: 'person',
            render: function RenderPersonLink(_: any, sessionRecording: SessionRecordingType) {
                return <PersonHeader withIcon person={sessionRecording.person} />
            },
        },

        {
            render: function RenderPlayButton(_: any, sessionRecording: SessionRecordingType) {
                return (
                    <div className="play-button-container">
                        <Button
                            className={sessionRecording.viewed ? 'play-button viewed' : 'play-button'}
                            data-attr="session-recordings-button"
                            icon={<PlayCircleOutlined />}
                        >
                            Watch recording
                        </Button>
                    </div>
                )
            },
        },
    ]
    return (
        <div className="session-recordings-table" data-attr="session-recordings-table">
            <Row className="filter-row">
                <div className="filter-container" style={{ display: showFilters ? undefined : 'none' }}>
                    <div>
                        <Typography.Text strong>
                            {`Filter by events and actions `}
                            <Tooltip title="Show recordings where all of the events or actions listed below happen.">
                                <InfoCircleOutlined className="info-icon" />
                            </Tooltip>
                        </Typography.Text>
                        <ActionFilter
                            fullWidth={true}
                            filters={entityFilters}
                            setFilters={(payload) => {
                                setEntityFilters(payload)
                            }}
                            typeKey={isPersonPage ? `person-${personUUID}` : 'session-recordings'}
                            hideMathSelector={true}
                            buttonCopy="Add another filter"
                            horizontalUI
                            stripeActionRow={false}
                            propertyFilterWrapperClassName="session-recording-action-property-filter"
                            customRowPrefix=""
                            hideRename
                            showOr
                            renderRow={(props) => <FilterRow {...props} />}
                            showNestedArrow={false}
                            actionsTaxonomicGroupTypes={[
                                TaxonomicFilterGroupType.Actions,
                                TaxonomicFilterGroupType.Events,
                            ]}
                            propertiesTaxonomicGroupTypes={[
                                TaxonomicFilterGroupType.EventProperties,
                                TaxonomicFilterGroupType.Elements,
                            ]}
                        />
                    </div>
                    {!isPersonPage && preflight?.is_clickhouse_enabled && (
                        <div className="mt-2">
                            <Typography.Text strong>
                                {`Filter by persons and cohorts `}
                                <Tooltip title="Show recordings by persons who match the set criteria">
                                    <InfoCircleOutlined className="info-icon" />
                                </Tooltip>
                            </Typography.Text>
                            <PropertyFilters
                                popoverPlacement="bottomRight"
                                pageKey={isPersonPage ? `person-${personUUID}` : 'session-recordings'}
                                taxonomicGroupTypes={[
                                    TaxonomicFilterGroupType.PersonProperties,
                                    TaxonomicFilterGroupType.Cohorts,
                                ]}
                                propertyFilters={propertyFilters}
                                onChange={(properties) => {
                                    setPropertyFilters(properties)
                                }}
                            />
                        </div>
                    )}
                </div>
                <Button
                    style={{ display: showFilters ? 'none' : undefined }}
                    onClick={() => {
                        enableFilter()
                        if (isPersonPage) {
                            const entityFilterButtons = document.querySelectorAll('.entity-filter-row button')
                            if (entityFilterButtons.length > 0) {
                                ;(entityFilterButtons[0] as HTMLElement).click()
                            }
                        }
                    }}
                >
                    <FilterOutlined /> Filter recordings
                </Button>

                <Row className="time-filter-row">
                    <Row className="time-filter">
                        <DateFilter
                            makeLabel={(key) => (
                                <>
                                    <CalendarOutlined />
                                    <span> {key}</span>
                                </>
                            )}
                            defaultValue="Last 7 days"
                            bordered={true}
                            dateFrom={fromDate ?? undefined}
                            dateTo={toDate ?? undefined}
                            onChange={(changedDateFrom, changedDateTo) => {
                                setDateRange(changedDateFrom, changedDateTo)
                            }}
                            dateOptions={{
                                Custom: { values: [] },
                                'Last 24 hours': { values: ['-24h'] },
                                'Last 7 days': { values: ['-7d'] },
                                'Last 21 days': { values: ['-21d'] },
                            }}
                        />
                    </Row>
                    <Row className="time-filter">
                        <Typography.Text className="filter-label">Duration</Typography.Text>
                        <DurationFilter
                            onChange={(newFilter) => {
                                setDurationFilter(newFilter)
                            }}
                            initialFilter={durationFilter}
                            pageKey={isPersonPage ? `person-${personUUID}` : 'session-recordings'}
                        />
                    </Row>
                </Row>
            </Row>

            <LemonTable
                dataSource={sessionRecordings}
                columns={columns}
                loading={sessionRecordingsResponseLoading}
                onRow={(sessionRecording) => ({
                    onClick: (e) => {
                        // Lets the link to the person open the person's page and not the session recording
                        if (!(e.target as HTMLElement).closest('a')) {
                            openSessionPlayer(sessionRecording.id, RecordingWatchedSource.RecordingsList)
                        }
                    },
                })}
                rowClassName="cursor-pointer"
                data-attr="session-recording-table"
            />
            {(hasPrev || hasNext) && (
                <Row className="pagination-control">
                    <Button
                        type="link"
                        disabled={!hasPrev}
                        onClick={() => {
                            loadPrev()
                            window.scrollTo(0, 0)
                        }}
                    >
                        <LeftOutlined /> Previous
                    </Button>
                    <Button
                        type="link"
                        disabled={!hasNext}
                        onClick={() => {
                            loadNext()
                            window.scrollTo(0, 0)
                        }}
                    >
                        Next <RightOutlined />
                    </Button>
                </Row>
            )}
            <div style={{ marginBottom: 64 }} />
            {!!sessionRecordingId && <SessionPlayerDrawer isPersonPage={isPersonPage} onClose={closeSessionPlayer} />}
        </div>
    )
}