@ant-design/icons#LinkOutlined TypeScript Examples

The following examples show how to use @ant-design/icons#LinkOutlined. 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: ActionAttribute.tsx    From posthog-foss with MIT License 6 votes vote down vote up
export function ActionAttribute({ attribute, value }: { attribute: string; value?: string }): JSX.Element {
    const icon =
        attribute === 'text' ? (
            <FontSizeOutlined />
        ) : attribute === 'href' ? (
            <LinkOutlined />
        ) : attribute === 'selector' ? (
            <BranchesOutlined />
        ) : (
            <FormOutlined />
        )

    const text =
        attribute === 'href' ? (
            <a href={value} target="_blank" rel="noopener noreferrer">
                {value}
            </a>
        ) : attribute === 'selector' ? (
            value ? (
                <span style={{ fontFamily: 'monospace' }}>
                    <SelectorString value={value} />
                </span>
            ) : (
                <span>
                    Could not generate a unique selector for this element. Please instrument it with a unique{' '}
                    <code>id</code> or <code>data-attr</code> attribute.
                </span>
            )
        ) : (
            value
        )

    return (
        <div key={attribute} style={{ marginBottom: 10, paddingLeft: 24, position: 'relative' }}>
            <div style={{ position: 'absolute', left: 2, top: 3, color: 'hsl(240, 14%, 50%)' }}>{icon}</div>
            <span>{text}</span>
        </div>
    )
}
Example #2
Source File: index.tsx    From XFlow with MIT License 6 votes vote down vote up
GraphToolbar = (props: Props) => {
  const { onAddNodeClick, onDeleteNodeClick, onConnectEdgeClick } = props
  const [selectedNodes, setSelectedNodes] = React.useState([])

  /** 监听画布中选中的节点 */
  const watchModelService = async () => {
    const appRef = useXFlowApp()
    const modelService = appRef && appRef?.modelService
    if (modelService) {
      const model = await MODELS.SELECTED_NODES.getModel(modelService)
      model.watch(async () => {
        const nodes = await MODELS.SELECTED_NODES.useValue(modelService)
        setSelectedNodes(nodes)
      })
    }
  }

  watchModelService()

  return (
    <div className="xflow-er-solution-toolbar">
      <div className="icon" onClick={() => onAddNodeClick()}>
        <span>添加节点</span>
        <PlusCircleOutlined />
      </div>
      <div className="icon" onClick={() => onConnectEdgeClick()}>
        <span>添加关系</span>
        <LinkOutlined />
      </div>
      <div
        className={`icon ${selectedNodes?.length > 0 ? '' : 'disabled'}`}
        onClick={() => onDeleteNodeClick()}
      >
        <span>删除节点</span>
        <DeleteOutlined />
      </div>
    </div>
  )
}
Example #3
Source File: StatusIcon.tsx    From datart with Apache License 2.0 6 votes vote down vote up
CanLinkageIcon: React.FC<{
  title: React.ReactNode | undefined;
}> = ({ title }) => {
  return (
    <Tooltip title={title}>
      <Button icon={<LinkOutlined style={{ color: PRIMARY }} />} type="link" />
    </Tooltip>
  );
}
Example #4
Source File: CustomInlineEditor.tsx    From dnde with GNU General Public License v3.0 5 votes vote down vote up
LinkItem = ({ setLinkCallback }: LinkItemProps) => {
  const [active, setActive] = useState(false);
  const [link, setLink] = useState('');
  const linkRef = useRef<any>(null);

  const onChange = (e: any) => {
    setLink(e.target.value);
  };

  const onMouseDown = (e: any) => {
    restoreSelection();
  };

  return (
    <Popover
      visible={active}
      content={
        <div style={{ display: 'flex', gap: '8px' }}>
          <Input
            className="inline-editor-link"
            ref={linkRef}
            value={link}
            onMouseDown={onMouseDown}
            onChange={onChange}
            placeholder="link"
          />
          <Button
            type="default"
            onMouseDown={ResetEventBehaviour}
            onClick={() => {
              setActive(false);
              restoreSelection();
              document.execCommand(
                'insertHTML',
                false,
                '<a href="' + addHttps(link) + '" target="_blank">' + document.getSelection() + '</a>'
              );
            }}
          >
            create
          </Button>
        </div>
      }
      trigger="click"
      placement="bottom"
    >
      <Button
        style={{ fontSize: '12px' }}
        onClick={(e) => {
          ResetEventBehaviour(e);
          setActive(!active);
          setLink('');
        }}
        size="small"
        icon={<LinkOutlined />}
      ></Button>
    </Popover>
  );
}
Example #5
Source File: ProfileCard.tsx    From office-hours with GNU General Public License v3.0 5 votes vote down vote up
export default function ProfileCard({
  name,
  role,
  linkedin,
  personalSite,
  imgSrc,
}: {
  name: string;
  role: string;
  linkedin?: string;
  personalSite?: string;
  imgSrc: string;
}): ReactElement {
  return (
    <StyledCard>
      <img width={200} alt={`${name}'s profile image`} src={imgSrc} />
      <ImageOverlay />
      <CardContents>
        <CardTitle>{name}</CardTitle>
        <div>{role}</div>
        <LinkIcons>
          {linkedin && (
            <NavyLink href={linkedin} target="_blank" rel="noopener noreferrer">
              <LinkedinFilled title="LinkedIn" style={{ cursor: "pointer" }} />
            </NavyLink>
          )}
          {personalSite && (
            <NavyLink
              href={personalSite}
              target="_blank"
              rel="noopener noreferrer"
            >
              <LinkOutlined
                title="Personal Website"
                style={{ cursor: "pointer" }}
              />
            </NavyLink>
          )}
        </LinkIcons>
      </CardContents>
    </StyledCard>
  );
}
Example #6
Source File: ContextItem.spec.tsx    From next-basics with GNU General Public License v3.0 5 votes vote down vote up
describe("ContextItem", () => {
  it("should work", () => {
    const handleItemClick = jest.fn();
    const handleItemDelete = jest.fn();
    const handleDropItem = jest.fn();
    const handleItemHover = jest.fn();
    const wrapper = shallow(
      <ContextItem
        data={{
          name: "data-b",
          value: {
            id: 1,
          },
        }}
        handleItemClick={handleItemClick}
        handleItemDelete={handleItemDelete}
        handleDropItem={handleDropItem}
        index={1}
        canDrag={true}
        handleItemHover={handleItemHover}
      />
    );
    expect(wrapper.find(CodeOutlined).length).toBe(1);
    wrapper.find(".deleteIcon").simulate("click");
    expect(handleItemDelete).toBeCalled();
    wrapper.setProps({
      data: {
        name: "data-a",
        resolve: {
          useProvider: "provider-a",
          args: ["args1"],
          if: false,
          transform: {
            value: "<% DATA %>",
          },
        },
      },
    });
    expect(wrapper.find(LinkOutlined).length).toBe(1);
    wrapper.find(".varItem").simulate("click");
    expect(handleItemClick).toBeCalled();
    wrapper.find(".varItem").invoke("onMouseEnter")({} as any);
    expect(handleItemHover).toBeCalledWith("data-a");
    handleItemHover.mockClear();
    wrapper.find(".varItem").invoke("onMouseLeave")({} as any);
    expect(handleItemHover).toBeCalledWith();
  });
});
Example #7
Source File: ContextItem.tsx    From next-basics with GNU General Public License v3.0 5 votes vote down vote up
export function ContextItem({
  index,
  data,
  canDrag,
  highlighted,
  handleDropItem,
  handleItemClick,
  handleItemDelete,
  handleItemHover,
}: ContextItemProps): React.ReactElement {
  const ref = useRef();
  const [{ isOver, dropClassName }, drop] = useDrop({
    accept: type,
    collect: (monitor) => {
      const { index: dragIndex } = monitor.getItem() || {};
      if (dragIndex === index) {
        return {};
      }
      return {
        isOver: monitor.isOver(),
        dropClassName:
          dragIndex < index
            ? `${styles.dropOverDownward}`
            : `${styles.dropOverUpward}`,
      };
    },
    drop: (item: any) => {
      handleDropItem(item.index, index);
    },
  });
  const [{ isDragging }, drag] = useDrag({
    item: { type, index },
    canDrag,
    collect: (monitor) => ({
      isDragging: monitor.isDragging(),
    }),
  });
  drop(drag(ref));

  const handleMouseEnter = (): void => {
    handleItemHover(data.name);
  };

  const handleMouseLeave = (): void => {
    handleItemHover();
  };

  return (
    <div
      ref={ref}
      className={classNames(styles.varItem, {
        [dropClassName]: isOver,
        [styles.highlighted]: highlighted,
      })}
      onClick={handleItemClick}
      key={data.name}
      onMouseEnter={handleMouseEnter}
      onMouseLeave={handleMouseLeave}
    >
      {data.resolve ? (
        <LinkOutlined style={{ color: "var(--theme-orange-color)" }} />
      ) : (
        <CodeOutlined style={{ color: "var(--theme-green-color)" }} />
      )}
      <span className={styles.varName}>{data.name}</span>
      <Button
        type="link"
        danger
        icon={<DeleteOutlined />}
        className={styles.deleteIcon}
        onClick={handleItemDelete}
      />
    </div>
  );
}
Example #8
Source File: EntityPicker.tsx    From jmix-frontend with Apache License 2.0 5 votes vote down vote up
export function EntityPicker(props: EntityPickerProps) {
  const {
    value, 
    entityName,
    onChange,
    disabled,
    propertyName,
    associationOptions
  } = props;

  const screens = useScreens();
  const intl = useIntl();
  const metadata = useMetadata();

  const propertyInfo = useMemo(() => {
    return getPropertyInfo(metadata.entities, entityName, propertyName);
  }, [metadata.entities, entityName, propertyName]);

  if(propertyInfo == null) {
    throw new Error(`Metadata not found for property ${propertyName} of entity ${entityName}`);
  }

  const isCorrectFiekd = useMemo(() => {
    return isToOneAssociation(propertyInfo)
  }, [propertyInfo]);

  if(!isCorrectFiekd) {
    throw new Error(`property must be a to-one association`);
  }

  const displayedValue = useMemo(() => {
    return value != null
      ? getDisplayedValue(value, associationOptions)
      : undefined
  }, [value, associationOptions])

  const onSelectEntity = useCallback((entityInstance?: Record<string, unknown>) => {
      if (onChange != null) {
        const newValue = entityInstance?.id != null
          ? entityInstance.id as string
          : undefined

        onChange(newValue);
      }
    }, [onChange, propertyInfo]);

  const handleClick = useCallback(() => {
    try{
      openCrudScreen({
        entityName: propertyInfo.type,
        crudScreenType: "entityList",
        screens,
        props: {
          onSelectEntity
        }
      })
    } catch(_e) {
      notifications.show({
        type: NotificationType.ERROR,
        description: intl.formatMessage({ id: "common.openScreenError" }, {entityName: propertyInfo.type})
      });
    }
  }, [entityName, screens, onSelectEntity, propertyInfo.type]);

  return (
    <Input 
      prefix={<LinkOutlined />}
      readOnly={true}
      onClick={handleClick}
      value={displayedValue}
      disabled={disabled}
      id={propertyName}
    />
  );
}
Example #9
Source File: Spacing.tsx    From yugong with MIT License 4 votes vote down vote up
Spacing: React.FC<Props> = ({ unit, onChange, margin, padding }) => {
  const [spaceType, setSpaceType] = useState<"margin" | "padding">("padding");

  const [inValues, setInValues] = useState<any[]>(defaultVal);

  const [outValues, setOutValues] = useState<any[]>(defaultVal);

  const [locked, setLocked] = useState<boolean>();

  useEffect(() => {
    setInValues(padding || defaultVal);
  }, [padding]);
  useEffect(() => {
    setOutValues(margin || defaultVal);
  }, [margin]);

  const onChangeValue = useCallback(
    (index: number) => (value: UnitType) => {
      if (spaceType === "padding") {
        const values: any[] = [...inValues];
        values[index] = value;
        if (locked === true) {
          values[1] = values[2] = values[3] = values[0] = value;
        }
        setInValues(values);
        onChange(spaceType, values);
      }
      if (spaceType === "margin") {
        const values = [...outValues];
        values[index] = value;
        if (locked === true) {
          values[1] = values[2] = values[3] = values[0] = value;
        }
        setOutValues(values);
        onChange(spaceType, values);
      }
    },
    [spaceType, inValues, locked, onChange, outValues]
  );

  const getValue = useCallback(
    (type = spaceType) => {
      let values: any[] = [];
      if (type === "padding") {
        values = inValues;
      }
      if (type === "margin") {
        values = outValues;
      }
      return values;
    },
    [inValues, outValues, spaceType]
  );

  const onChangeType = useCallback(
    (type: SpaceType) => (e: any) => {
      e.stopPropagation();
      setSpaceType(type);
      const values = getValue(type);
      const unEqu = values.filter((item) => values[0] !== item);
      if (!!unEqu.length) {
        setLocked(false);
      } else {
        setLocked(true);
      }
    },
    [getValue, setLocked]
  );

  const onToggleLocker = useCallback(() => {
    setLocked(!locked);
  }, [locked]);

  const setLabel = useCallback((index: number) => {
    switch (index) {
      case 0:
        return "顶部";
      case 1:
        return "右边";
      case 2:
        return "底部";
      case 3:
        return "左边";
      default:
        return "";
    }
  }, []);

  return (
    <>
      <Divider orientation="left"><span className={s.divide}>边距</span></Divider>
      <Row gutter={4}>
        <Col span={9}>
          <div
            className={s.boxA}
            onClick={onChangeType("margin")}
            style={
              spaceType === "margin"
                ? { backgroundColor: "#fff" }
                : { backgroundColor: "#eee" }
            }
          >
            <div
              className={s.boxB}
              onClick={onChangeType("padding")}
              style={
                spaceType === "padding"
                  ? { backgroundColor: "#fff" }
                  : { backgroundColor: "#eee" }
              }
            />
          </div>
        </Col>
        <Col span={3} className={s.middle}>
          <LinkOutlined
            onClick={onToggleLocker}
            className={locked ? s.locked : undefined}
          />
        </Col>
        <Col span={12}>
          {getValue().map((item, index) => {
            return <Unitinput
              span={{ label: 3, wrapper: 21 }}
              key={`${spaceType}${index}`}
              className={s.unititem}
              label={setLabel(index)}
              defaultValue={item}
              onChange={onChangeValue(index)}
            />;
          })}
        </Col>
      </Row>
    </>
  );
}
Example #10
Source File: FileManager.tsx    From anew-server with MIT License 4 votes vote down vote up
FileManager: React.FC<FileManagerProps> = (props) => {
    const { modalVisible, handleChange, connectId } = props;
    const [columnData, setColumnData] = useState<API.SSHFileList[]>([]);
    const [showHidden, setShowHidden] = useState<boolean>(false);
    const [childrenDrawer, setChildrenDrawer] = useState<boolean>(false);
    const [currentPathArr, setCurrentPathArr] = useState<string[]>([]);
    const [initPath, setInitPath] = useState<string>('');

    const _dirSort = (item: API.SSHFileList) => {
        return item.isDir;
    };

    const getFileData = (key: string, path: string) => {
        querySSHFile(key, path).then((res) => {
            const obj = lds.orderBy(res.data, [_dirSort, 'name'], ['desc', 'asc']);
            showHidden ? setColumnData(obj) : setColumnData(obj.filter((x) => !x.name.startsWith('.')));
            try {
                // 获取服务器的当前路径
                let pathb = obj[0].path;
                const index = pathb.lastIndexOf('/');
                pathb = pathb.substring(0, index + 1);
                setCurrentPathArr(pathb.split('/').filter((x: any) => x !== ''));
                setInitPath(pathb); // 保存当前路径,刷新用
            } catch (exception) {
                setCurrentPathArr(path.split('/').filter((x) => x !== ''));
                setInitPath(path);
            }
        });
    };

    const getChdirDirData = (key: string, path: string) => {
        const index = currentPathArr.indexOf(path);
        const currentDir = '/' + currentPathArr.splice(0, index + 1).join('/');
        getFileData(key, currentDir);
    };

    const handleDelete = (key: string, path: string) => {
        if (!path) return;
        const index = path.lastIndexOf('/');
        const currentDir = path.substring(0, index + 1);
        const currentFile = path.substring(index + 1, path.length);
        const content = `您是否要删除 ${currentFile}?`;
        Modal.confirm({
            title: '注意',
            content,
            onOk: () => {
                deleteSSHFile(key, path).then((res) => {
                    if (res.code === 200 && res.status === true) {
                        message.success(res.message);
                        getFileData(key, currentDir);
                    }
                });
            },
            onCancel() { },
        });
    };

    const handleDownload = (key: string, path: string) => {
        if (!path) return;
        const index = path.lastIndexOf('/');
        const currentFile = path.substring(index + 1, path.length);
        const content = `您是否要下载 ${currentFile}?`;
        Modal.confirm({
            title: '注意',
            content,
            onOk: () => {
                const token = localStorage.getItem('token');
                const link = document.createElement('a');
                link.href = `/api/v1/host/ssh/download?key=${key}&path=${path}&token=${token}`;
                document.body.appendChild(link);
                const evt = document.createEvent('MouseEvents');
                evt.initEvent('click', false, false);
                link.dispatchEvent(evt);
                document.body.removeChild(link);
            },
            onCancel() { },
        });
    };

    const uploadProps = {
        name: 'file',
        action: `/api/v1/host/ssh/upload?key=${connectId}&path=${initPath}`,
        multiple: true,
        headers: {
            Authorization: `Bearer ${localStorage.getItem('token')}`,
        },
        // showUploadList: {
        //   removeIcon: false,
        //   showRemoveIcon: false,
        // },
        onChange(info: any) {
            // if (info.file.status !== 'uploading') {
            //   console.log(info.file, info.fileList);
            // }
            //console.log(info);
            if (info.file.status === 'done') {
                message.success(`${info.file.name} file uploaded successfully`);
                getFileData(connectId, initPath as string); // 刷新数据
            } else if (info.file.status === 'error') {
                message.error(`${info.file.name} file upload failed.`);
            }
        },
        progress: {
            strokeColor: {
                '0%': '#108ee9',
                '100%': '#87d068',
            },
            strokeWidth: 3,
            format: (percent: any) => `${parseFloat(percent.toFixed(2))}%`,
        },
    };


    const columns: ProColumns<API.SSHFileList>[] = [
        {
            title: '名称',
            dataIndex: 'name',
            render: (_, record) =>
                record.isDir ? (
                    <div onClick={() => getFileData(connectId, record.path)} style={{ cursor: 'pointer' }}>
                        <FolderTwoTone />
                        <span style={{ color: '#1890ff', paddingLeft: 5 }}>{record.name}</span>
                    </div>
                ) : (
                    <React.Fragment>
                        {record.isLink ? (
                            <div>
                                <LinkOutlined />
                                <Tooltip title="Is Link">
                                    <span style={{ color: '#3cb371', paddingLeft: 5 }}>{record.name}</span>
                                </Tooltip>
                            </div>
                        ) : (
                            <div>
                                <FileOutlined />
                                <span style={{ paddingLeft: 5 }}>{record.name}</span>
                            </div>
                        )}
                    </React.Fragment>
                ),
        },
        {
            title: '大小',
            dataIndex: 'size',
        },
        {
            title: '修改时间',
            dataIndex: 'mtime',
        },
        {
            title: '属性',
            dataIndex: 'mode',
        },
        {
            title: '操作',
            dataIndex: 'option',
            valueType: 'option',
            render: (_, record) =>
                !record.isDir && !record.isLink ? (
                    <>
                        <Tooltip title="下载文件">
                            <DownloadOutlined
                                style={{ fontSize: '17px', color: 'blue' }}
                                onClick={() => handleDownload(connectId, record.path)}
                            />
                        </Tooltip>
                        <Divider type="vertical" />
                        <Tooltip title="删除文件">
                            <DeleteOutlined
                                style={{ fontSize: '17px', color: 'red' }}
                                onClick={() => handleDelete(connectId, record.path)}
                            />
                        </Tooltip>
                    </>
                ) : null,
        },
    ];

    useEffect(() => {
        // 是否显示隐藏文件
        getFileData(connectId, initPath as string); // 刷新数据
    }, [showHidden]);

    const { Dragger } = Upload;
    return (
        <Drawer
            title="文件管理器"
            placement="right"
            width={800}
            visible={modalVisible}
            onClose={()=>handleChange(false)}
            getContainer={false}
        >
            {/* <input style={{ display: 'none' }} type="file" ref={(ref) => (this.input = ref)} /> */}
            <div className={styles.drawerHeader}>
                <Breadcrumb>
                    <Breadcrumb.Item href="#" onClick={() => getFileData(connectId, '/')}>
                        <ApartmentOutlined />
                    </Breadcrumb.Item>
                    <Breadcrumb.Item href="#" onClick={() => getFileData(connectId, '')}>
                        <HomeOutlined />
                    </Breadcrumb.Item>
                    {currentPathArr.map((item) => (
                        <Breadcrumb.Item key={item} href="#" onClick={() => getChdirDirData(connectId, item)}>
                            <span>{item}</span>
                        </Breadcrumb.Item>
                    ))}
                </Breadcrumb>
                <div style={{ display: 'flex', alignItems: 'center' }}>
                    <span>显示隐藏文件:</span>
                    <Switch
                        checked={showHidden}
                        checkedChildren="开启"
                        unCheckedChildren="关闭"
                        onChange={(v) => {
                            setShowHidden(v);
                        }}
                    />

                    <Button
                        style={{ marginLeft: 10 }}
                        size="small"
                        type="primary"
                        icon={<UploadOutlined />}
                        onClick={() => setChildrenDrawer(true)}
                    >
                        上传文件
                    </Button>
                </div>
            </div>
            <Drawer
                title="上传文件"
                width={320}
                closable={false}
                onClose={() => setChildrenDrawer(false)}
                visible={childrenDrawer}
            >
                <div style={{ height: 150 }}>
                    <Dragger {...uploadProps}>
                        <p className="ant-upload-drag-icon">
                            <InboxOutlined />
                        </p>
                        <p className="ant-upload-text">单击或拖入上传</p>
                        <p className="ant-upload-hint">支持多文件</p>
                    </Dragger>
                </div>
            </Drawer>
            <ProTable
                pagination={false}
                search={false}
                toolBarRender={false}
                rowKey="name"
                dataSource={columnData}
                columns={columns}
            />
        </Drawer>
    );
}
Example #11
Source File: LinkageFields.tsx    From datart with Apache License 2.0 4 votes vote down vote up
LinkageFields: React.FC<LinkageFieldsProps> = memo(
  ({ form, viewMap, curWidget, chartGroupColumns }) => {
    const t = useI18NPrefix(`viz.linkage`);
    const renderOptions = useCallback(
      (index: number, key: 'triggerViewId' | 'linkerViewId') => {
        const viewLinkages: ViewLinkageItem[] =
          form?.getFieldValue('viewLinkages');
        if (!viewLinkages) {
          return null;
        }
        if (key === 'triggerViewId') {
          return chartGroupColumns?.map(item => (
            <Option
              key={item.uid}
              fieldvaluetype={item.type}
              value={item.colName}
            >
              <div style={{ display: 'flex', justifyContent: 'space-between' }}>
                <span>{item.colName}</span>
                <FieldType>{item.type}</FieldType>
              </div>
            </Option>
          ));
        } else if (key === 'linkerViewId') {
          return viewMap[viewLinkages[index][key]].meta
            ?.filter(item => {
              const enableTypes = [
                DataViewFieldType.STRING,
                DataViewFieldType.DATE,
              ];
              return item.type && enableTypes.includes(item.type);
            })
            .map(item => (
              <Option key={item.id} fieldvaluetype={item.type} value={item.id}>
                <div
                  style={{ display: 'flex', justifyContent: 'space-between' }}
                >
                  <span>{item.id}</span>
                  <FieldType>{item.type}</FieldType>
                </div>
              </Option>
            ));
        }
      },
      [chartGroupColumns, form, viewMap],
    );
    const getItem = useCallback(
      (index: number) => {
        const viewLinkages: ViewLinkageItem[] =
          form?.getFieldValue('viewLinkages');
        return viewLinkages[index];
      },
      [form],
    );
    const getLinkerView = useCallback(
      (index: number) => {
        const viewLinkages: ViewLinkageItem[] =
          form?.getFieldValue('viewLinkages');
        if (!viewLinkages) {
          return null;
        }
        return viewMap[viewLinkages[index].linkerViewId];
      },
      [form, viewMap],
    );
    return (
      <Wrapper>
        <Divider orientation="left">{t('associatedFields')}</Divider>

        <div>
          {t('dataSource')} : {viewMap[curWidget?.viewIds?.[0]]?.name}
        </div>
        <Form.List name="viewLinkages">
          {(fields, _, { errors }) => {
            return (
              <FormWrap>
                {fields.map((field, index) => (
                  <Form.Item noStyle key={index} shouldUpdate>
                    <div className="form-item">
                      <div className="form-item-start">
                        <Form.Item
                          {...field}
                          style={{ display: 'inline-block' }}
                          shouldUpdate
                          validateTrigger={['onChange', 'onClick', 'onBlur']}
                          name={[field.name, 'triggerColumn']}
                          fieldKey={[field.fieldKey, 'id']}
                          rules={[
                            { required: true, message: t('selectTriggers') },
                          ]}
                        >
                          <Select
                            style={{ width: 200 }}
                            showSearch
                            placeholder={t('selectTriggers')}
                            allowClear
                          >
                            {renderOptions(index, 'triggerViewId')}
                          </Select>
                        </Form.Item>
                      </div>
                      <div className="form-item-and">
                        <LinkOutlined />
                      </div>

                      <div className="form-item-endValue">
                        <Form.Item
                          {...field}
                          style={{ display: 'inline-block' }}
                          shouldUpdate
                          validateTrigger={['onChange', 'onClick', 'onBlur']}
                          name={[field.name, 'linkerColumn']}
                          rules={[
                            { required: true, message: t('selectLinker') },
                          ]}
                          fieldKey={[field.fieldKey, 'id']}
                        >
                          <Select
                            style={{ width: 200 }}
                            showSearch
                            placeholder={t('selectLinker')}
                            allowClear
                          >
                            {renderOptions(index, 'linkerViewId')}
                          </Select>
                        </Form.Item>
                        <span className="ViewName">
                          {' '}
                          ( {getLinkerView(index)?.name} {' / '}
                          {getItem(index)?.linkerName})
                        </span>
                      </div>
                    </div>
                  </Form.Item>
                ))}
                <Form.Item>
                  <Form.ErrorList errors={errors} />
                </Form.Item>
                {!fields.length && <Empty key="empty" />}
              </FormWrap>
            );
          }}
        </Form.List>
      </Wrapper>
    );
  },
)
Example #12
Source File: WidgetActionDropdown.tsx    From datart with Apache License 2.0 4 votes vote down vote up
WidgetActionDropdown: React.FC<WidgetActionDropdownProps> = memo(
  ({ widget }) => {
    const { editing: boardEditing } = useContext(BoardContext);

    const widgetAction = useWidgetAction();
    const dataChart = useContext(WidgetChartContext)!;
    const t = useI18NPrefix(`viz.widget.action`);
    const menuClick = useCallback(
      ({ key }) => {
        widgetAction(key, widget);
      },
      [widgetAction, widget],
    );
    const getAllList = useCallback(() => {
      const allWidgetActionList: WidgetActionListItem<widgetActionType>[] = [
        {
          key: 'refresh',
          label: t('refresh'),
          icon: <SyncOutlined />,
        },
        {
          key: 'fullScreen',
          label: t('fullScreen'),
          icon: <FullscreenOutlined />,
        },
        {
          key: 'edit',
          label: t('edit'),
          icon: <EditOutlined />,
        },
        {
          key: 'delete',
          label: t('delete'),
          icon: <DeleteOutlined />,
          danger: true,
        },

        {
          key: 'info',
          label: t('info'),
          icon: <InfoOutlined />,
        },
        {
          key: 'lock',
          label: t('lock'),
          icon: <LockOutlined />,
        },

        {
          key: 'makeLinkage',
          label: t('makeLinkage'),
          icon: <LinkOutlined />,
          divider: true,
        },
        {
          key: 'closeLinkage',
          label: t('closeLinkage'),
          icon: <CloseCircleOutlined />,
          danger: true,
        },
        {
          key: 'makeJump',
          label: t('makeJump'),
          icon: <BranchesOutlined />,
          divider: true,
        },
        {
          key: 'closeJump',
          label: t('closeJump'),
          icon: <CloseCircleOutlined />,
          danger: true,
        },
      ];
      return allWidgetActionList;
    }, [t]);
    const actionList = useMemo(() => {
      return (
        getWidgetActionList({
          allList: getAllList(),
          widget,
          boardEditing,
          chartGraphId: dataChart?.config?.chartGraphId,
        }) || []
      );
    }, [boardEditing, dataChart?.config?.chartGraphId, getAllList, widget]);
    const dropdownList = useMemo(() => {
      const menuItems = actionList.map(item => {
        return (
          <React.Fragment key={item.key}>
            {item.divider && <Menu.Divider />}
            <Menu.Item
              danger={item.danger}
              icon={item.icon}
              disabled={item.disabled}
              key={item.key}
            >
              {item.label}
            </Menu.Item>
          </React.Fragment>
        );
      });

      return <Menu onClick={menuClick}>{menuItems}</Menu>;
    }, [actionList, menuClick]);
    if (actionList.length === 0) {
      return null;
    }
    return (
      <Dropdown
        className="widget-tool-dropdown"
        overlay={dropdownList}
        placement="bottomCenter"
        trigger={['click']}
        arrow
      >
        <Button icon={<EllipsisOutlined />} type="link" />
      </Dropdown>
    );
  },
)
Example #13
Source File: index.tsx    From dashboard with Apache License 2.0 4 votes vote down vote up
AutoReply: React.FC<AutoReplyProps> = (props) => {
  const {welcomeMsg, setWelcomeMsg, isFetchDone} = props;
  const [modalVisible, setModalVisible] = useState(false);
  const [attachments, setAttachments] = useState<Attachment[]>([]);
  const [currentIndex, setCurrentIndex] = useState<number>(0);
  const [currentMode, setCurrentMode] = useState<MsgType>('image');
  const [linkFetching, setLinkFetching] = useState(false);
  const [content, setContent] = useState('');
  const contentRef = useRef<React.RefObject<HTMLElement>>();
  const imageModalFormRef = useRef<FormInstance>();
  const linkModalFormRef = useRef<FormInstance>();
  const miniAppModalFormRef = useRef<FormInstance>();

  const UploadFileFn = async (req: UploadRequestOption, ref: MutableRefObject<any | undefined>, inputName: string) => {
    const file = req.file as File;
    if (!file.name) {
      message.error('非法参数');
      return;
    }

    const hide = message.loading('上传中');
    try {
      const res = await GetSignedURL(file.name)
      const data = res.data as GetSignedURLResult
      if (res.code === 0) {
        const uploadRes = (await fetch(data.upload_url, {
          method: 'PUT',
          body: file
        }));
        hide();
        if (uploadRes.ok && ref) {
          ref.current?.setFieldsValue({[inputName]: data.download_url});
          return;
        }

        message.error('上传图片失败');

        return;
      }

      hide();
      message.error('获取上传地址失败');
      return;

    } catch (e) {
      message.error('上传图片失败');
      console.log(e);
    }
  };

  useEffect(() => {
    const formData = itemDataToFormData(welcomeMsg);
    setAttachments(formData.attachments || []);
    setContent(formData.text || '');
  }, [isFetchDone]);

  useEffect(() => {
    setWelcomeMsg({
      text: content || '',
      attachments: attachments || [],
    });
  }, [content, attachments]);

  return (
    <>
      <div className={styles.replyEditor}>
        <div className={'preview-container'}>
          <div className={styles.replyEditorPreview}>
            <img src={phoneImage} className='bg'/>
            <div className='content'>
              <ul className='reply-list'>
                {content && (
                  <li><img
                    src={avatarDefault}/>
                    <div className='msg text' dangerouslySetInnerHTML={{__html: content}}/>
                  </li>
                )}
                {attachments && attachments.length > 0 && (
                  attachments.map((attachment) => {
                    if (attachment.msgtype === 'image') {
                      return (
                        <li key={attachment.id}>
                          <img src={avatarDefault}/>
                          <div className={`msg image`}>
                            <img src={attachment.image?.pic_url}/>
                          </div>
                        </li>
                      );
                    }

                    if (attachment.msgtype === 'link') {
                      return (
                        <li key={attachment.id}>
                          <img src={avatarDefault}/>
                          <div className='msg link'><p className='title'>{attachment.link?.title}</p>
                            <div className='link-inner'><p
                              className='desc'>{attachment.link?.desc}</p>
                              <img src={attachment.link?.picurl}/>
                            </div>
                          </div>
                        </li>
                      );
                    }

                    if (attachment.msgtype === 'miniprogram') {
                      return (
                        <li key={attachment.id}>
                          <img src={avatarDefault}/>
                          <div className='msg miniprogram'>
                            <p className='m-title'>
                              <IconFont
                                type={'icon-weixin-mini-app'}
                                style={{marginRight: 4, fontSize: 14}}
                              />
                              {attachment.miniprogram?.title}
                            </p>
                            <img src={attachment.miniprogram?.pic_media_id}/>
                            <p className='l-title'>
                              <IconFont type={'icon-weixin-mini-app'} style={{marginRight: 4}}/>
                              小程序
                            </p>
                          </div>
                        </li>
                      );
                    }

                    return '';
                  })
                )}
              </ul>
            </div>
          </div>
        </div>

        <div className='text-area-container'>
          <div className={styles.msgTextareaContainer} style={{border: 'none'}}>
            {props.enableQuickInsert && (
              <div className='insert-btn '>
                    <span
                      className='clickable no-select'
                      onClick={() => {
                        setContent(`${content}[客户昵称]`);
                      }}
                    >[插入客户昵称]</span>
              </div>
            )}
            <div className='textarea-container '>
              <ContentEditable
                // @ts-ignore
                innerRef={contentRef}
                onKeyDown={(event) => {
                  if (event.key === 'Enter') {
                    document.execCommand('insertLineBreak');
                    event.preventDefault();
                  }
                }}
                className={'textarea'}
                html={content}
                onChange={(e) => {
                  setContent(e.target.value);
                }}/>
              <div className='flex-row align-side'>
                <p className='text-cnt'>{content.length}/600</p>
              </div>
            </div>
          </div>
        </div>
        <div className='option-area-container'>
          {attachments && attachments.length > 0 && (
            <ReactSortable handle={'.draggable-button'} tag='ul' className={'select-msg-options'} list={attachments} setList={setAttachments}>
              {attachments.map((attachment, index) => (
                <li key={attachment.id} className='flex-row'>
                      <span>
                        <MinusCircleOutlined
                          onClick={() => {
                            const items = [...attachments];
                            items.splice(index, 1);
                            setAttachments(items);
                          }}
                        />
                        【{msgTypes[attachment.msgtype]}】:
                        <span
                          className='col-1'>{attachment?.name}</span>
                      </span>
                  <span className='d-action-container'>
                      <EditOutlined
                        onClick={() => {
                          setCurrentMode(attachment.msgtype);
                          imageModalFormRef.current?.setFieldsValue(attachment.image);
                          linkModalFormRef.current?.setFieldsValue(attachment.link);
                          miniAppModalFormRef.current?.setFieldsValue(attachment.miniprogram);
                          setCurrentIndex(index);
                          setModalVisible(true);
                        }}
                      />
                      <DragOutlined
                        className={'draggable-button'}
                        style={{cursor: 'grabbing'}}
                      />
                    </span>
                </li>
              ))}
            </ReactSortable>
          )}
          <div className='option-container'>
            <Dropdown
              placement='topLeft'
              trigger={['click']}
              overlay={(
                <Menu style={{minWidth: 120}}>
                  <Menu.Item
                    key={'image'}
                    icon={<FileImageOutlined/>}
                    onClick={() => {
                      setCurrentMode('image');
                      setCurrentIndex(attachments.length);
                      imageModalFormRef.current?.resetFields();
                      setModalVisible(true);
                    }}
                  >
                    图片
                  </Menu.Item>
                  <Menu.Item
                    key={'link'}
                    icon={<LinkOutlined/>}
                    onClick={() => {
                      setCurrentMode('link');
                      setCurrentIndex(attachments.length);
                      setModalVisible(true);
                    }}
                  >
                    链接
                  </Menu.Item>
                  <Menu.Item
                    key={'miniApp'}
                    icon={<IconFont type={'icon-weixin-mini-app'}/>}
                    onClick={() => {
                      setCurrentMode('miniprogram');
                      setCurrentIndex(attachments.length);
                      setModalVisible(true);
                    }}
                  >
                    小程序
                  </Menu.Item>
                </Menu>
              )}
            >
              <a className='ant-dropdown-link' onClick={e => e.preventDefault()}>
                <PlusCircleOutlined/> 添加附件
              </a>
            </Dropdown>
          </div>
        </div>
      </div>

      <ModalForm
        formRef={imageModalFormRef}
        className={'dialog from-item-label-100w'}
        layout={'horizontal'}
        width={'560px'}
        visible={currentMode === 'image' && modalVisible}
        onVisibleChange={setModalVisible}
        onFinish={async (params: { title: string, pic_url: string, msgtype: MsgType }) => {
          attachments[currentIndex] = {
            id: new Date().getTime().toString(),
            msgtype: params.msgtype,
            name: params.title,
            image: {...params},
          };
          setAttachments(attachments);
          return true;
        }}
      >
        <h2 className='dialog-title'> 添加图片附件 </h2>
        <ProForm.Item initialValue={'image'} name={'msgtype'} noStyle={true}>
          <input type={'hidden'}/>
        </ProForm.Item>
        <ProFormText
          name='title'
          label='图片名称'
          placeholder={'请输入图片名称'}
          width='md'
          rules={[
            {
              required: true,
              message: '请输入图片名称!',
            },
          ]}
        />
        <Form.Item
          label='上传图片'
          name='pic_url'
          rules={[
            {
              required: true,
              message: '请上传图片!',
            },
          ]}
        >
          <ImageUploader
            customRequest={async (req) => {
              await UploadFileFn(req, imageModalFormRef, 'pic_url')
            }}
          />
        </Form.Item>
      </ModalForm>


      <ModalForm
        formRef={linkModalFormRef}
        className={'dialog from-item-label-100w'}
        layout={'horizontal'}
        width={'560px'}
        visible={currentMode === 'link' && modalVisible}
        onVisibleChange={setModalVisible}
        onFinish={async (params) => {
          attachments[currentIndex] = {
            id: new Date().getTime().toString(),
            msgtype: params.msgtype,
            name: params.title,
            // @ts-ignore
            link: {...params},
          };
          setAttachments(attachments);
          return true;
        }}
      >
        <Spin spinning={linkFetching}>
          <h2 className='dialog-title'> 添加链接附件 </h2>
          <ProForm.Item initialValue={'link'} name={'msgtype'} noStyle={true}>
            <input type={'hidden'}/>
          </ProForm.Item>
          <ProFormText
            name='url'
            label='链接地址'
            width='md'
            fieldProps={{
              disabled: linkFetching,
              addonAfter: (
                <Tooltip title="点击抓取远程链接,自动填充标题,描述,图片">
                  <div
                    onClick={async () => {
                      setLinkFetching(true);
                      const res = await ParseURL(linkModalFormRef.current?.getFieldValue('url'))
                      setLinkFetching(false);
                      if (res.code !== 0) {
                        message.error(res.message);
                      } else {
                        message.success('解析链接成功');
                        linkModalFormRef?.current?.setFieldsValue({
                          customer_link_enable: 1,
                          title: res.data.title,
                          desc: res.data.desc,
                          picurl: res.data.img_url,
                        })
                      }
                    }}
                    style={{
                      cursor: "pointer",
                      width: 32,
                      height: 30,
                      display: 'flex',
                      alignItems: 'center',
                      justifyContent: 'center'
                    }}>
                    <SyncOutlined/>
                  </div>
                </Tooltip>
              )
            }}
            rules={[
              {
                required: true,
                message: '请输入链接地址',
              },
              {
                type: 'url',
                message: '请填写正确的的URL,必须是http或https开头',
              },
            ]}
          />
          <ProFormSwitch
            label={'高级设置'}
            checkedChildren='开启'
            unCheckedChildren='关闭'
            name='customer_link_enable'
            tooltip={'开启后可以自定义链接所有信息'}
          />
          <ProFormDependency name={['customer_link_enable']}>
            {({customer_link_enable}) => {
              if (customer_link_enable) {
                return (
                  <>
                    <ProFormText
                      name='title'
                      label='链接标题'
                      width='md'
                      rules={[
                        {
                          required: true,
                          message: '请输入链接标题',
                        },
                      ]}
                    />
                    <ProFormTextArea
                      name='desc'
                      label='链接描述'
                      width='md'
                    />
                    <Form.Item
                      label='链接封面'
                      name='picurl'
                      rules={[
                        {
                          required: true,
                          message: '请上传链接图片!',
                        },
                      ]}
                    >
                      <ImageUploader
                        customRequest={async (req) => {
                          await UploadFileFn(req, linkModalFormRef, 'picurl')
                        }}
                      />
                    </Form.Item>
                  </>
                );
              }
              return <></>;
            }}
          </ProFormDependency>
        </Spin>
      </ModalForm>


      <ModalForm
        formRef={miniAppModalFormRef}
        className={'dialog from-item-label-100w'}
        layout={'horizontal'}
        width={'560px'}
        labelCol={{
          md: 6,
        }}
        visible={currentMode === 'miniprogram' && modalVisible}
        onVisibleChange={setModalVisible}
        onFinish={async (params) => {
          attachments[currentIndex] = {
            id: new Date().getTime().toString(),
            msgtype: params.msgtype,
            name: params.title,
            // @ts-ignore
            miniprogram: {...params},
          };
          setAttachments(attachments);
          return true;
        }}
      >
        <h2 className='dialog-title'> 添加小程序附件 </h2>

        <Alert
          showIcon={true}
          type='info'
          message={
            '请填写企业微信后台绑定的小程序id和路径,否则会造成发送失败'
          }
          style={{marginBottom: 20}}
        />

        <ProForm.Item initialValue={'miniprogram'} name={'msgtype'} noStyle={true}>
          <input type={'hidden'}/>
        </ProForm.Item>

        <ProFormText
          name='title'
          label='小程序标题'
          width='md'
          rules={[
            {
              required: true,
              message: '请输入链接标题',
            },
          ]}
        />

        <ProFormText
          // 帮助指引
          name='app_id'
          label='小程序AppID'
          width='md'
          rules={[
            {
              required: true,
              message: '请输入小程序AppID',
            },
          ]}
        />

        <ProFormText
          name='page'
          label='小程序路径'
          width='md'
          rules={[
            {
              required: true,
              message: '请输入小程序路径',
            },
          ]}
        />

        <Form.Item
          label='小程序封面'
          name='pic_media_id'
          rules={[
            {
              required: true,
              message: '请小程序封面!',
            },
          ]}
        >
          <ImageUploader
            customRequest={async (req) => {
              await UploadFileFn(req, miniAppModalFormRef, 'pic_media_id')
            }}
          />
        </Form.Item>

      </ModalForm>

    </>
  );
}
Example #14
Source File: chartGroup.tsx    From fe-v5 with Apache License 2.0 4 votes vote down vote up
// 根据chartConfigModal配置的数据进行展示

export default function ChartGroup(props: Props) {
  const { t } = useTranslation();
  const {
    id,
    cluster,
    busiId,
    groupInfo,
    range,
    step,
    variableConfig,
    onAddChart,
    onUpdateChart,
    onCloneChart,
    onShareChart,
    onDelChartGroup,
    onDelChart,
    onUpdateChartGroup,
    onMoveUpChartGroup,
    onMoveDownChartGroup,
    moveUpEnable,
    moveDownEnable,
  } = props;
  const [chartConfigs, setChartConfigs] = useState<Chart[]>([]);
  const [Refs, setRefs] = useState<RefObject<any>[]>();
  const [layout, setLayout] = useState<Layouts>(layouts); // const [colItem, setColItem] = useState<number>(defColItem);
  const [mounted, setMounted] = useState<boolean>(false);
  useEffect(() => {
    init();
    Refs &&
      Refs.forEach((ref) => {
        const graphInstance = ref.current;
        graphInstance && graphInstance.refresh();
      });
  }, [groupInfo.updateTime, cluster]);

  const init = async () => {
    setMounted(false);
    getCharts(busiId, groupInfo.id).then(async (res) => {
      let charts = res.dat
        ? res.dat.map((item: { configs: string; id: any; weight: any; group_id: number }) => {
            let configs = item.configs ? JSON.parse(item.configs) : {};
            return { id: item.id, configs, weight: item.weight, group_id: item.group_id };
          })
        : [];
      let haveNewChart = false;
      const innerLayout = charts.map((item: { configs: { layout: { i: string; x?: number; y?: number; w: number; h: number } } }, index: string | number) => {
        if (item.configs.layout) {
          // 当Chart被删除后 layout中的i会中断,ResponsiveReactGridLayout会有问题
          item.configs.layout.i = '' + index;
          // 克隆图表后 layout 不具备 x/y 值,需要计算设置
          if (item.configs.layout.x === undefined && item.configs.layout.y === undefined) {
            haveNewChart = true;
            return getNewItemLayout(
              charts.slice(0, index).map(
                (item: {
                  configs: {
                    layout: any;
                  };
                }) => item.configs.layout,
              ),
              Number(index),
              {
                w: item.configs.layout.w,
                h: item.configs.layout.h,
              },
            );
          }
          return item.configs.layout;
        } else {
          haveNewChart = true;
          return getNewItemLayout(
            charts.slice(0, index).map(
              (item: {
                configs: {
                  layout: any;
                };
              }) => item.configs.layout,
            ),
            Number(index),
          );
        }
      });

      if (haveNewChart) {
        const { dat } = await getPerm(busiId, 'rw');
        dat &&
          updateCharts(
            busiId,
            charts.map((item, index) => {
              const { h, w, x, y, i } = innerLayout[index];
              item.configs.layout = { h, w, x, y, i };
              return item;
            }),
          );
      }

      const realLayout: Layouts = { lg: innerLayout, sm: innerLayout, md: innerLayout, xs: innerLayout, xxs: innerLayout };
      setLayout(realLayout);
      setChartConfigs(charts);
      setRefs(new Array(charts.length).fill(0).map((_) => React.createRef()));
      setMounted(true);
    });
  };

  const getNewItemLayout = function (curentLayouts: Array<Layout>, index: number, size?: { w: number; h: number }): Layout {
    const { w, h } = size || { w: unit, h: unit / 3 };
    const layoutArrayLayoutFillArray = new Array<Array<number>>();
    curentLayouts.forEach((layoutItem) => {
      if (layoutItem) {
        const { w, h, x, y } = layoutItem;
        for (let i = 0; i < h; i++) {
          if (typeof layoutArrayLayoutFillArray[i + y] === 'undefined') {
            layoutArrayLayoutFillArray[i + y] = new Array<number>(cols).fill(0);
          }

          for (let k = 0; k < w; k++) {
            layoutArrayLayoutFillArray[i + y][k + x] = 1;
          }
        }
      }
    });
    let nextLayoutX = -1;
    let nextLayoutY = -1; // 填充空行

    for (let i = 0; i < layoutArrayLayoutFillArray.length; i++) {
      if (typeof layoutArrayLayoutFillArray[i] === 'undefined') {
        layoutArrayLayoutFillArray[i] = new Array<number>(cols).fill(0);
      }
    }

    function isEmpty(i: number, j: number) {
      let flag = true;

      for (let x = i; x < i + w; x++) {
        for (let y = j; y < j + h; y++) {
          if (layoutArrayLayoutFillArray[x] && layoutArrayLayoutFillArray[x][y]) {
            flag = false;
          }
        }
      }

      return flag;
    }

    for (let i = 0; i < layoutArrayLayoutFillArray.length - 1; i++) {
      for (let j = 0; j <= cols - unit; j++) {
        if (isEmpty(i, j)) {
          nextLayoutY = i;
          nextLayoutX = j;
          break;
        }
      }
    }

    if (nextLayoutX === -1) {
      nextLayoutX = 0;
      nextLayoutY = layoutArrayLayoutFillArray.length;
    }

    return { w, h, x: nextLayoutX, y: nextLayoutY, i: '' + index };
  };

  const onLayoutChange = async (val: { h: any; w: any; x: any; y: any; i: any }[]) => {
    if (val.length === 0) return;
    let needUpdate = false;
    const { lg: lgLayout } = layout;

    for (var k = 0; k < val.length; k++) {
      const { h, w, x, y, i } = val[k];
      const { h: oldh, w: oldw, x: oldx, y: oldy, i: oldi } = lgLayout[k];
      if (h !== oldh || w !== oldw || x !== oldx || y !== oldy || i !== oldi) {
        needUpdate = true;
      }
    }
    if (!needUpdate) return;
    let currConfigs = chartConfigs.map((item, index) => {
      const { h, w, x, y, i } = val[index];
      item.configs.layout = { h, w, x, y, i };
      return item;
    });

    // setLayout({ lg: [...layout], sm: [...layout], md: [...layout], xs: [...layout], xxs: [...layout] });

    const { dat } = await getPerm(busiId, 'rw');
    dat && updateCharts(busiId, currConfigs);
  };

  const setArrange = async (colItem: number, w = cols / colItem, h = unit / 3) => {
    setMounted(false);
    let countX = 0;
    let countY = 0;
    const _lg: Layout[] = [];
    [...layout.lg].forEach((ele, index) => {
      let innerObj = { ...ele };

      if (index + 1 > colItem) {
        let c = (index + 1) / colItem;
        countY = Math.trunc(c) * h;
      }

      innerObj.w = w;
      innerObj.h = h;
      innerObj.x = countX;
      innerObj.y = countY;
      countX += innerObj.w;

      if ((index + 1) % colItem === 0) {
        countX = 0;
      }

      _lg.push(innerObj);
    });
    let currConfigs = chartConfigs.map((item, index) => {
      const { h, w, x, y, i } = _lg[index];
      item.configs.layout = { h, w, x, y, i };
      return item;
    });
    const { dat } = await getPerm(busiId, 'rw');
    dat && updateCharts(busiId, currConfigs);
    setLayout({ lg: [..._lg], sm: [..._lg], md: [..._lg], xs: [..._lg], xxs: [..._lg] });
    setChartConfigs(currConfigs);
    setMounted(true);
  };

  function handleMenuClick(e) {
    e.domEvent.stopPropagation();
    setArrange(Number(e.key));
  }

  function menu() {
    const { t } = useTranslation();
    let listArr: ReactElement[] = [];

    for (let i = 1; i <= defColItem; i++) {
      let item = (
        <Menu.Item key={i}>
          {i}
          {t('列')}
        </Menu.Item>
      );
      listArr.push(item);
    }

    return <Menu onClick={handleMenuClick}>{listArr}</Menu>;
  }

  const generateRightButton = () => {
    const { t } = useTranslation();
    return (
      <>
        <Button
          type='link'
          size='small'
          onClick={(event) => {
            event.stopPropagation();
            onAddChart(groupInfo.id);
          }}
        >
          {t('新增图表')}
        </Button>
        <Divider type='vertical' />
        <Dropdown overlay={menu()}>
          <Button
            type='link'
            size='small'
            onClick={(event) => {
              event.stopPropagation();
            }}
          >
            {t('一键规整')}
            <DownOutlined />
          </Button>
        </Dropdown>
        <Divider type='vertical' />
        <Button
          type='link'
          size='small'
          onClick={(event) => {
            event.stopPropagation();
            onUpdateChartGroup(groupInfo);
          }}
        >
          {t('修改')}
        </Button>
        <Divider type='vertical' />
        <Button
          type='link'
          size='small'
          disabled={!moveUpEnable}
          onClick={(event) => {
            event.stopPropagation();
            onMoveUpChartGroup(groupInfo);
          }}
        >
          {t('上移')}
        </Button>
        <Divider type='vertical' />
        <Button
          type='link'
          size='small'
          disabled={!moveDownEnable}
          onClick={(event) => {
            event.stopPropagation();
            onMoveDownChartGroup(groupInfo);
          }}
        >
          {t('下移')}
        </Button>
        <Divider type='vertical' />
        <Button
          type='link'
          size='small'
          onClick={(event) => {
            event.stopPropagation();
            confirm({
              title: `${t('是否删除分类')}:${groupInfo.name}`,
              onOk: async () => {
                onDelChartGroup(groupInfo.id);
              },

              onCancel() {},
            });
          }}
        >
          {t('删除')}
        </Button>
      </>
    );
  };

  const generateDOM = () => {
    return (
      chartConfigs &&
      chartConfigs.length > 0 &&
      chartConfigs.map((item, i) => {
        let { QL, name, legend, yplotline1, yplotline2, highLevelConfig, version } = item.configs;
        if (semver.valid(version)) {
          // 新版图表配置的版本使用语义化版本规范
          const { type } = item.configs as any;
          return (
            <div
              style={{
                border: '1px solid #e0dee2',
              }}
              key={String(i)}
            >
              <Renderer
                dashboardId={id}
                id={item.id}
                time={range}
                refreshFlag={props.refreshFlag}
                step={step}
                type={type}
                values={item.configs as any}
                variableConfig={variableConfig}
                onCloneClick={() => {
                  onCloneChart(groupInfo, item);
                }}
                onShareClick={() => {
                  onShareChart(groupInfo, item);
                }}
                onEditClick={() => {
                  onUpdateChart(groupInfo, item);
                }}
                onDeleteClick={() => {
                  confirm({
                    title: `${t('是否删除图表')}:${item.configs.name}`,
                    onOk: async () => {
                      onDelChart(groupInfo, item);
                    },
                  });
                }}
              />
            </div>
          );
        }
        const promqls = QL.map((item) =>
          variableConfig && variableConfig.var && variableConfig.var.length ? replaceExpressionVars(item.PromQL, variableConfig, variableConfig.var.length, id) : item.PromQL,
        );
        const legendTitleFormats = QL.map((item) => item.Legend);
        return (
          <div
            style={{
              border: '1px solid #e0dee2',
            }}
            key={String(i)}
          >
            <Graph
              ref={Refs![i]}
              highLevelConfig={highLevelConfig}
              data={{
                yAxis: {
                  plotLines: [
                    {
                      value: yplotline1 ? yplotline1 : undefined,
                      color: 'orange',
                    },
                    {
                      value: yplotline2 ? yplotline2 : undefined,
                      color: 'red',
                    },
                  ],
                },
                legend: legend,
                step,
                range,
                title: (
                  <Tooltip title={name}>
                    <span>{name}</span>
                  </Tooltip>
                ),
                promqls,
                legendTitleFormats,
              }}
              extraRender={(graph) => {
                return (
                  <>
                    <Button
                      type='link'
                      size='small'
                      onClick={(e) => {
                        e.preventDefault();
                        window.open(item.configs.link);
                      }}
                      disabled={!item.configs.link}
                    >
                      <LinkOutlined />
                    </Button>
                    <Button
                      type='link'
                      size='small'
                      onClick={(e) => {
                        e.preventDefault();
                        onUpdateChart(groupInfo, item);
                      }}
                    >
                      <EditOutlined />
                    </Button>

                    <Button
                      type='link'
                      size='small'
                      onClick={(e) => {
                        e.preventDefault();
                        confirm({
                          title: `${t('是否删除图表')}:${item.configs.name}`,
                          onOk: async () => {
                            onDelChart(groupInfo, item);
                          },

                          onCancel() {},
                        });
                      }}
                    >
                      <CloseCircleOutlined />
                    </Button>
                  </>
                );
              }}
            />
          </div>
        );
      })
    );
  };

  const renderCharts = useCallback(() => {
    const { t } = useTranslation();
    return (
      <div
        style={{
          width: '100%',
        }}
      >
        {chartConfigs && chartConfigs.length > 0 ? (
          <ResponsiveReactGridLayout
            cols={{ lg: cols, sm: cols, md: cols, xs: cols, xxs: cols }}
            layouts={layout}
            onLayoutChange={onLayoutChange}
            measureBeforeMount={false}
            useCSSTransforms={false}
            preventCollision={false}
            isBounded={true}
            draggableHandle='.graph-header'
          >
            {generateDOM()}
          </ResponsiveReactGridLayout>
        ) : (
          <p className='empty-group-holder'>Now it is empty</p>
        )}
      </div>
    );
  }, [mounted, groupInfo.updateTime, range, variableConfig, step]);
  return (
    <div className='n9e-dashboard-group'>
      <Collapse defaultActiveKey={['0']}>
        <Panel header={<span className='panel-title'>{groupInfo.name}</span>} key='0' extra={generateRightButton()}>
          {renderCharts()}
        </Panel>
      </Collapse>
    </div>
  );
}
Example #15
Source File: index.tsx    From fe-v5 with Apache License 2.0 4 votes vote down vote up
function index(props: IProps) {
  const { dashboardId, id, time, refreshFlag, step, type, variableConfig, values, isPreview, onCloneClick, onShareClick, onEditClick, onDeleteClick } = props;
  const ref = useRef<HTMLDivElement>(null);
  const [inViewPort] = useInViewport(ref);
  const { series, loading } = usePrometheus({
    id,
    dashboardId,
    time,
    refreshFlag,
    step,
    targets: values.targets,
    variableConfig,
    inViewPort: isPreview || inViewPort,
  });
  const subProps = {
    values,
    series,
  };
  const tipsVisible = values.description || !_.isEmpty(values.links);
  if (_.isEmpty(values)) return null;
  const RendererCptMap = {
    timeseries: () => <Timeseries {...subProps} />,
    stat: () => <Stat {...subProps} />,
    table: () => <Table {...subProps} />,
    pie: () => <Pie {...subProps} />,
  };

  return (
    <div className='renderer-container' ref={ref}>
      <div className='renderer-header graph-header dashboards-panels-item-drag-handle'>
        {tipsVisible ? (
          <Tooltip
            placement='rightTop'
            overlayInnerStyle={{
              width: 300,
            }}
            title={
              <div>
                <Markdown content={values.description} />
                <div>
                  {_.map(values.links, (link) => {
                    return (
                      <div style={{ marginTop: 8 }}>
                        <a href={link.url} target={link.targetBlank ? '_blank' : '_self'}>
                          {link.title}
                        </a>
                      </div>
                    );
                  })}
                </div>
              </div>
            }
          >
            <div className='renderer-header-desc'>
              <span className='renderer-header-info-corner-inner' />
              {values.description ? <InfoOutlined /> : <LinkOutlined />}
            </div>
          </Tooltip>
        ) : null}
        <div className='renderer-header-content'>
          {!isPreview ? (
            <Dropdown
              trigger={['click']}
              placement='bottomCenter'
              overlayStyle={{
                minWidth: '100px',
              }}
              overlay={
                <Menu>
                  {!isPreview ? (
                    <>
                      <Menu.Item onClick={onEditClick} key='0'>
                        <SettingOutlined style={{ marginRight: 8 }} />
                        编辑
                      </Menu.Item>
                      <Menu.Item onClick={onCloneClick} key='1'>
                        <CopyOutlined style={{ marginRight: 8 }} />
                        克隆
                      </Menu.Item>
                      <Menu.Item onClick={onShareClick} key='2'>
                        <ShareAltOutlined style={{ marginRight: 8 }} />
                        分享
                      </Menu.Item>
                      <Menu.Item onClick={onDeleteClick} key='3'>
                        <DeleteOutlined style={{ marginRight: 8 }} />
                        删除
                      </Menu.Item>
                    </>
                  ) : null}
                </Menu>
              }
            >
              <div className='renderer-header-title'>
                {values.name}
                <DownOutlined className='renderer-header-arrow' />
              </div>
            </Dropdown>
          ) : (
            <div className='renderer-header-title'>{values.name}</div>
          )}
        </div>
        <div className='renderer-header-loading'>{loading && <SyncOutlined spin />}</div>
      </div>
      <div className='renderer-body' style={{ height: `calc(100% - 36px)` }}>
        {RendererCptMap[type] ? RendererCptMap[type]() : `无效的图表类型 ${type}`}
      </div>
    </div>
  );
}
Example #16
Source File: index.tsx    From fe-v5 with Apache License 2.0 4 votes vote down vote up
function index(props: IProps) {
  const { dashboardId, id, time, refreshFlag, step, type, variableConfig, isPreview, onCloneClick, onShareClick, onEditClick, onDeleteClick } = props;
  const values = _.cloneDeep(props.values);
  const ref = useRef<HTMLDivElement>(null);
  const [inViewPort] = useInViewport(ref);
  const { series, loading } = usePrometheus({
    id,
    dashboardId,
    time,
    refreshFlag,
    step,
    targets: values.targets,
    variableConfig,
    inViewPort: isPreview || inViewPort,
  });
  const tipsVisible = values.description || !_.isEmpty(values.links);
  if (_.isEmpty(values)) return null;
  // TODO: 如果 hexbin 的 colorRange 为 string 时转成成 array
  if (typeof _.get(values, 'custom.colorRange') === 'string') {
    _.set(values, 'custom.colorRange', _.split(_.get(values, 'custom.colorRange'), ','));
  }
  const subProps = {
    values,
    series,
  };
  const RendererCptMap = {
    timeseries: () => <Timeseries {...subProps} />,
    stat: () => <Stat {...subProps} />,
    table: () => <Table {...subProps} />,
    pie: () => <Pie {...subProps} />,
    hexbin: () => <Hexbin {...subProps} />,
  };

  return (
    <div className='renderer-container' ref={ref}>
      <div className='renderer-header graph-header dashboards-panels-item-drag-handle'>
        {tipsVisible ? (
          <Tooltip
            placement='rightTop'
            overlayInnerStyle={{
              width: 300,
            }}
            title={
              <div>
                <Markdown content={values.description} />
                <div>
                  {_.map(values.links, (link, i) => {
                    return (
                      <div key={i} style={{ marginTop: 8 }}>
                        <a href={link.url} target={link.targetBlank ? '_blank' : '_self'}>
                          {link.title}
                        </a>
                      </div>
                    );
                  })}
                </div>
              </div>
            }
          >
            <div className='renderer-header-desc'>
              <span className='renderer-header-info-corner-inner' />
              {values.description ? <InfoOutlined /> : <LinkOutlined />}
            </div>
          </Tooltip>
        ) : null}
        <div className='renderer-header-content'>
          {!isPreview ? (
            <Dropdown
              trigger={['click']}
              placement='bottomCenter'
              overlayStyle={{
                minWidth: '100px',
              }}
              overlay={
                <Menu>
                  {!isPreview ? (
                    <>
                      <Menu.Item onClick={onEditClick} key='0'>
                        <SettingOutlined style={{ marginRight: 8 }} />
                        编辑
                      </Menu.Item>
                      <Menu.Item onClick={onCloneClick} key='1'>
                        <CopyOutlined style={{ marginRight: 8 }} />
                        克隆
                      </Menu.Item>
                      <Menu.Item onClick={onShareClick} key='2'>
                        <ShareAltOutlined style={{ marginRight: 8 }} />
                        分享
                      </Menu.Item>
                      <Menu.Item onClick={onDeleteClick} key='3'>
                        <DeleteOutlined style={{ marginRight: 8 }} />
                        删除
                      </Menu.Item>
                    </>
                  ) : null}
                </Menu>
              }
            >
              <div className='renderer-header-title'>
                {values.name}
                <DownOutlined className='renderer-header-arrow' />
              </div>
            </Dropdown>
          ) : (
            <div className='renderer-header-title'>{values.name}</div>
          )}
        </div>
        <div className='renderer-header-loading'>{loading && <SyncOutlined spin />}</div>
      </div>
      <div className='renderer-body' style={{ height: `calc(100% - 36px)` }}>
        {RendererCptMap[type] ? RendererCptMap[type]() : `无效的图表类型 ${type}`}
      </div>
    </div>
  );
}
Example #17
Source File: BorderRadius.tsx    From yugong with MIT License 4 votes vote down vote up
BorderRadius: React.FC<Props> = ({
  unit,
  onChange,
  defaultData,
  ...other
}) => {
  const [locked, setLocked] = useState(false);

  const [topLeft, setTopLeft] = useState<UnitType>();
  const [topRight, setTopRight] = useState<UnitType>();
  const [bottomRight, setBottomRight] = useState<UnitType>();
  const [bottomLeft, setBottomLeft] = useState<UnitType>();

  useEffect(() => {
    if (Array.isArray(defaultData)) {
      setTopLeft(defaultData[0]);
      setTopRight(defaultData[1]);
      setBottomRight(defaultData[2]);
      setBottomLeft(defaultData[3]);
    }
  }, [defaultData]);

  const toggleLocked = useCallback(() => {
    if (!locked) {
      setTopLeft(topLeft);
      setTopRight(topLeft);
      setBottomRight(topLeft);
      setBottomLeft(topLeft);
    }
    setLocked(!locked);
    if (onChange instanceof Function) {
      onChange([topLeft, topLeft, topLeft, topLeft]);
    }
  }, [locked, onChange, topLeft]);

  const onChangeAll = useCallback((value: UnitType) => {
    setTopLeft(value);
    setTopRight(value);
    setBottomRight(value);
    setBottomLeft(value);
  }, []);

  const updateChange = useCallback((value: (UnitType | undefined)[]) => {
    if (onChange instanceof Function) {
      onChange(value)
    }
  }, [onChange])

  const onChangeData = useCallback(
    (index: number) => (value: UnitType) => {
      if (locked) {
        onChangeAll(value);
        updateChange([value, value, value, value]);
        return;
      }
      switch (index) {
        case 0:
          setTopLeft(value);
          updateChange([value, topRight, bottomRight, bottomLeft]);
          break;
        case 1:
          setTopRight(value);
          updateChange([topLeft, value, bottomRight, bottomLeft]);
          break;
        case 2:
          setBottomRight(value);
          updateChange([topLeft, topRight, value, bottomLeft]);
          break;
        case 3:
          setBottomLeft(value);
          updateChange([topLeft, topRight, bottomRight, value]);
          break;
        default:
          break;
      }
    },
    [bottomLeft, bottomRight, locked, onChangeAll, topLeft, topRight, updateChange]
  );

  return (
    <Row className={s.row}>
      <Col span={11}>
        <Row>
          <Col span={4} className={classNames(s.icon, s.alignright)}>
            <RadiusUpleftOutlined />
            &nbsp;
          </Col>
          <Col span={20}>
            <UnitInput
              min={0}
              span={{ wrapper: 24 }}
              defaultValue={topLeft}
              onChange={onChangeData(0)}
            />
          </Col>
        </Row>
        <Row>
          <Col span={4} className={classNames(s.icon, s.alignright)}>
            <RadiusBottomleftOutlined />
            &nbsp;
          </Col>
          <Col span={20}>
            <UnitInput
              min={0}
              span={{ wrapper: 24 }}
              defaultValue={bottomLeft}
              onChange={onChangeData(3)}
            />
          </Col>
        </Row>
      </Col>
      <Col span={2} className={s.middle}>
        <LinkOutlined
          onClick={toggleLocked}
          className={locked ? s.locked : undefined}
        />
      </Col>
      <Col span={11}>
        <Row className={s.row}>
          <Col span={20}>
            <UnitInput
              min={0}
              span={{ wrapper: 24 }}
              defaultValue={topRight}
              onChange={onChangeData(1)}
            />
          </Col>
          <Col span={4} className={s.icon}>
            &nbsp;
            <RadiusUprightOutlined />
          </Col>
        </Row>
        <Row className={s.row}>
          <Col span={20}>
            <UnitInput
              min={0}
              span={{ wrapper: 24 }}
              defaultValue={bottomRight}
              onChange={onChangeData(2)}
            />
          </Col>
          <Col span={4} className={s.icon}>
            &nbsp;
            <RadiusBottomrightOutlined />
          </Col>
        </Row>
      </Col>
    </Row>
  );
}
Example #18
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 #19
Source File: commandPaletteLogic.ts    From posthog-foss with MIT License 4 votes vote down vote up
commandPaletteLogic = kea<
    commandPaletteLogicType<
        Command,
        CommandFlow,
        CommandRegistrations,
        CommandResult,
        CommandResultDisplayable,
        RegExpCommandPairs
    >
>({
    path: ['lib', 'components', 'CommandPalette', 'commandPaletteLogic'],
    connect: {
        actions: [personalAPIKeysLogic, ['createKey']],
        values: [teamLogic, ['currentTeam'], userLogic, ['user']],
        logic: [preflightLogic], // used in afterMount, which does not auto-connect
    },
    actions: {
        hidePalette: true,
        showPalette: true,
        togglePalette: true,
        setInput: (input: string) => ({ input }),
        onArrowUp: true,
        onArrowDown: (maxIndex: number) => ({ maxIndex }),
        onMouseEnterResult: (index: number) => ({ index }),
        onMouseLeaveResult: true,
        executeResult: (result: CommandResult) => ({ result }),
        activateFlow: (flow: CommandFlow | null) => ({ flow }),
        backFlow: true,
        registerCommand: (command: Command) => ({ command }),
        deregisterCommand: (commandKey: string) => ({ commandKey }),
        setCustomCommand: (commandKey: string) => ({ commandKey }),
        deregisterScope: (scope: string) => ({ scope }),
    },
    reducers: {
        isPaletteShown: [
            false,
            {
                hidePalette: () => false,
                showPalette: () => true,
                togglePalette: (previousState) => !previousState,
            },
        ],
        keyboardResultIndex: [
            0,
            {
                setInput: () => 0,
                executeResult: () => 0,
                activateFlow: () => 0,
                backFlow: () => 0,
                onArrowUp: (previousIndex) => (previousIndex > 0 ? previousIndex - 1 : 0),
                onArrowDown: (previousIndex, { maxIndex }) => (previousIndex < maxIndex ? previousIndex + 1 : maxIndex),
            },
        ],
        hoverResultIndex: [
            null as number | null,
            {
                activateFlow: () => null,
                backFlow: () => null,
                onMouseEnterResult: (_, { index }) => index,
                onMouseLeaveResult: () => null,
                onArrowUp: () => null,
                onArrowDown: () => null,
            },
        ],
        input: [
            '',
            {
                setInput: (_, { input }) => input,
                activateFlow: () => '',
                backFlow: () => '',
                executeResult: () => '',
            },
        ],
        activeFlow: [
            null as CommandFlow | null,
            {
                activateFlow: (currentFlow, { flow }) =>
                    flow ? { ...flow, scope: flow.scope ?? currentFlow?.scope, previousFlow: currentFlow } : null,
                backFlow: (currentFlow) => currentFlow?.previousFlow ?? null,
            },
        ],
        rawCommandRegistrations: [
            {} as CommandRegistrations,
            {
                registerCommand: (commands, { command }) => {
                    return { ...commands, [command.key]: command }
                },
                deregisterCommand: (commands, { commandKey }) => {
                    const { [commandKey]: _, ...cleanedCommands } = commands // eslint-disable-line
                    return cleanedCommands
                },
            },
        ],
    },

    listeners: ({ actions, values }) => ({
        showPalette: () => {
            posthog.capture('palette shown', { isMobile: isMobile() })
        },
        togglePalette: () => {
            if (values.isPaletteShown) {
                posthog.capture('palette shown', { isMobile: isMobile() })
            }
        },
        executeResult: ({ result }: { result: CommandResult }) => {
            if (result.executor === true) {
                actions.activateFlow(null)
                actions.hidePalette()
            } else {
                const possibleFlow = result.executor?.() || null
                actions.activateFlow(possibleFlow)
                if (!possibleFlow) {
                    actions.hidePalette()
                }
            }
            // Capture command execution, without useless data
            // eslint-disable-next-line @typescript-eslint/no-unused-vars
            const { icon, index, ...cleanedResult }: Record<string, any> = result
            // eslint-disable-next-line @typescript-eslint/no-unused-vars
            const { resolver, ...cleanedCommand } = cleanedResult.source
            cleanedResult.source = cleanedCommand
            cleanedResult.isMobile = isMobile()
            posthog.capture('palette command executed', cleanedResult)
        },
        deregisterScope: ({ scope }) => {
            for (const command of Object.values(values.commandRegistrations)) {
                if (command.scope === scope) {
                    actions.deregisterCommand(command.key)
                }
            }
        },
        setInput: async ({ input }, breakpoint) => {
            await breakpoint(300)
            if (input.length > 8) {
                const response = await api.get('api/person/?key_identifier=' + encodeURIComponent(input))
                const person = response.results[0]
                if (person) {
                    actions.registerCommand({
                        key: `person-${person.distinct_ids[0]}`,
                        resolver: [
                            {
                                icon: UserOutlined,
                                display: `View person ${input}`,
                                executor: () => {
                                    const { push } = router.actions
                                    push(urls.person(person.distinct_ids[0]))
                                },
                            },
                        ],
                        scope: GLOBAL_COMMAND_SCOPE,
                    })
                }
            }
        },
    }),
    selectors: {
        isSqueak: [
            (selectors) => [selectors.input],
            (input: string) => {
                return input.trim().toLowerCase() === 'squeak'
            },
        ],
        activeResultIndex: [
            (selectors) => [selectors.keyboardResultIndex, selectors.hoverResultIndex],
            (keyboardResultIndex: number, hoverResultIndex: number | null) => {
                return hoverResultIndex ?? keyboardResultIndex
            },
        ],
        commandRegistrations: [
            (selectors) => [
                selectors.rawCommandRegistrations,
                dashboardsModel.selectors.nameSortedDashboards,
                teamLogic.selectors.currentTeam,
            ],
            (rawCommandRegistrations: CommandRegistrations, dashboards: DashboardType[]): CommandRegistrations => ({
                ...rawCommandRegistrations,
                custom_dashboards: {
                    key: 'custom_dashboards',
                    resolver: dashboards.map((dashboard: DashboardType) => ({
                        key: `dashboard_${dashboard.id}`,
                        icon: LineChartOutlined,
                        display: `Go to Dashboard: ${dashboard.name}`,
                        executor: () => {
                            const { push } = router.actions
                            push(urls.dashboard(dashboard.id))
                        },
                    })),
                    scope: GLOBAL_COMMAND_SCOPE,
                },
            }),
        ],
        regexpCommandPairs: [
            (selectors) => [selectors.commandRegistrations],
            (commandRegistrations: CommandRegistrations) => {
                const array: RegExpCommandPairs = []
                for (const command of Object.values(commandRegistrations)) {
                    if (command.prefixes) {
                        array.push([new RegExp(`^\\s*(${command.prefixes.join('|')})(?:\\s+(.*)|$)`, 'i'), command])
                    } else {
                        array.push([null, command])
                    }
                }
                return array
            },
        ],
        commandSearchResults: [
            (selectors) => [
                selectors.isPaletteShown,
                selectors.regexpCommandPairs,
                selectors.input,
                selectors.activeFlow,
                selectors.isSqueak,
            ],
            (
                isPaletteShown: boolean,
                regexpCommandPairs: RegExpCommandPairs,
                argument: string,
                activeFlow: CommandFlow | null,
                isSqueak: boolean
            ) => {
                if (!isPaletteShown || isSqueak) {
                    return []
                }
                if (activeFlow) {
                    return resolveCommand(activeFlow, argument)
                }
                let directResults: CommandResult[] = []
                let prefixedResults: CommandResult[] = []
                for (const [regexp, command] of regexpCommandPairs) {
                    if (regexp) {
                        const match = argument.match(regexp)
                        if (match && match[1]) {
                            prefixedResults = [...prefixedResults, ...resolveCommand(command, match[2], match[1])]
                        }
                    }
                    directResults = [...directResults, ...resolveCommand(command, argument)]
                }
                const allResults = directResults.concat(prefixedResults)
                let fusableResults: CommandResult[] = []
                let guaranteedResults: CommandResult[] = []
                for (const result of allResults) {
                    if (result.guarantee) {
                        guaranteedResults.push(result)
                    } else {
                        fusableResults.push(result)
                    }
                }
                fusableResults = uniqueBy(fusableResults, (result) => result.display)
                guaranteedResults = uniqueBy(guaranteedResults, (result) => result.display)
                const fusedResults = argument
                    ? new Fuse(fusableResults, {
                          keys: ['display', 'synonyms'],
                      })
                          .search(argument)
                          .slice(0, RESULTS_MAX)
                          .map((result) => result.item)
                    : sample(fusableResults, RESULTS_MAX - guaranteedResults.length)
                return guaranteedResults.concat(fusedResults)
            },
        ],
        commandSearchResultsGrouped: [
            (selectors) => [selectors.commandSearchResults, selectors.activeFlow],
            (commandSearchResults: CommandResult[], activeFlow: CommandFlow | null) => {
                const resultsGrouped: {
                    [scope: string]: CommandResult[]
                } = {}
                if (activeFlow) {
                    resultsGrouped[activeFlow.scope ?? '?'] = []
                }
                for (const result of commandSearchResults) {
                    const scope: string = result.source.scope ?? '?'
                    if (!(scope in resultsGrouped)) {
                        resultsGrouped[scope] = []
                    } // Ensure there's an array to push to
                    resultsGrouped[scope].push({ ...result })
                }
                let rollingGroupIndex = 0
                let rollingResultIndex = 0
                const resultsGroupedInOrder: [string, CommandResultDisplayable[]][] = []
                for (const [group, results] of Object.entries(resultsGrouped)) {
                    resultsGroupedInOrder.push([group, []])
                    for (const result of results) {
                        resultsGroupedInOrder[rollingGroupIndex][1].push({ ...result, index: rollingResultIndex++ })
                    }
                    rollingGroupIndex++
                }
                return resultsGroupedInOrder
            },
        ],
    },

    events: ({ actions }) => ({
        afterMount: () => {
            const { push } = router.actions

            const goTo: Command = {
                key: 'go-to',
                scope: GLOBAL_COMMAND_SCOPE,
                prefixes: ['open', 'visit'],
                resolver: [
                    {
                        icon: FundOutlined,
                        display: 'Go to Dashboards',
                        executor: () => {
                            push(urls.dashboards())
                        },
                    },
                    {
                        icon: RiseOutlined,
                        display: 'Go to Insights',
                        executor: () => {
                            push(urls.savedInsights())
                        },
                    },
                    {
                        icon: RiseOutlined,
                        display: 'Go to Trends',
                        executor: () => {
                            // TODO: Don't reset insight on change
                            push(urls.insightNew({ insight: InsightType.TRENDS }))
                        },
                    },
                    {
                        icon: FunnelPlotOutlined,
                        display: 'Go to Funnels',
                        executor: () => {
                            // TODO: Don't reset insight on change
                            push(urls.insightNew({ insight: InsightType.FUNNELS }))
                        },
                    },
                    {
                        icon: GatewayOutlined,
                        display: 'Go to Retention',
                        executor: () => {
                            // TODO: Don't reset insight on change
                            push(urls.insightNew({ insight: InsightType.RETENTION }))
                        },
                    },
                    {
                        icon: InteractionOutlined,
                        display: 'Go to Paths',
                        executor: () => {
                            // TODO: Don't reset insight on change
                            push(urls.insightNew({ insight: InsightType.PATHS }))
                        },
                    },
                    {
                        icon: ContainerOutlined,
                        display: 'Go to Events',
                        executor: () => {
                            push(urls.events())
                        },
                    },
                    {
                        icon: AimOutlined,
                        display: 'Go to Actions',
                        executor: () => {
                            push(urls.actions())
                        },
                    },
                    {
                        icon: UserOutlined,
                        display: 'Go to Persons',
                        synonyms: ['people'],
                        executor: () => {
                            push(urls.persons())
                        },
                    },
                    {
                        icon: UsergroupAddOutlined,
                        display: 'Go to Cohorts',
                        executor: () => {
                            push(urls.cohorts())
                        },
                    },
                    {
                        icon: FlagOutlined,
                        display: 'Go to Feature Flags',
                        synonyms: ['feature flags', 'a/b tests'],
                        executor: () => {
                            push(urls.featureFlags())
                        },
                    },
                    {
                        icon: MessageOutlined,
                        display: 'Go to Annotations',
                        executor: () => {
                            push(urls.annotations())
                        },
                    },
                    {
                        icon: TeamOutlined,
                        display: 'Go to Team members',
                        synonyms: ['organization', 'members', 'invites', 'teammates'],
                        executor: () => {
                            push(urls.organizationSettings())
                        },
                    },
                    {
                        icon: ProjectOutlined,
                        display: 'Go to Project settings',
                        executor: () => {
                            push(urls.projectSettings())
                        },
                    },
                    {
                        icon: SmileOutlined,
                        display: 'Go to My settings',
                        synonyms: ['account'],
                        executor: () => {
                            push(urls.mySettings())
                        },
                    },
                    {
                        icon: ApiOutlined,
                        display: 'Go to Plugins',
                        synonyms: ['integrations'],
                        executor: () => {
                            push(urls.plugins())
                        },
                    },
                    {
                        icon: DatabaseOutlined,
                        display: 'Go to System status page',
                        synonyms: ['redis', 'celery', 'django', 'postgres', 'backend', 'service', 'online'],
                        executor: () => {
                            push(urls.systemStatus())
                        },
                    },
                    {
                        icon: PlusOutlined,
                        display: 'Create action',
                        executor: () => {
                            push(urls.createAction())
                        },
                    },
                    {
                        icon: LogoutOutlined,
                        display: 'Log out',
                        executor: () => {
                            userLogic.actions.logout()
                        },
                    },
                ],
            }

            const debugClickhouseQueries: Command = {
                key: 'debug-clickhouse-queries',
                scope: GLOBAL_COMMAND_SCOPE,
                resolver:
                    userLogic.values.user?.is_staff ||
                    userLogic.values.user?.is_impersonated ||
                    preflightLogic.values.preflight?.is_debug ||
                    preflightLogic.values.preflight?.instance_preferences?.debug_queries
                        ? {
                              icon: PlusOutlined,
                              display: 'Debug queries (ClickHouse)',
                              executor: () => {
                                  debugCHQueries()
                              },
                          }
                        : [],
            }

            const calculator: Command = {
                key: 'calculator',
                scope: GLOBAL_COMMAND_SCOPE,
                resolver: (argument) => {
                    // don't try evaluating if there's no argument or if it's a plain number already
                    if (!argument || !isNaN(+argument)) {
                        return null
                    }
                    try {
                        const result = +Parser.evaluate(argument)
                        return isNaN(result)
                            ? null
                            : {
                                  icon: CalculatorOutlined,
                                  display: `= ${result}`,
                                  guarantee: true,
                                  executor: () => {
                                      copyToClipboard(result.toString(), 'calculation result')
                                  },
                              }
                    } catch {
                        return null
                    }
                },
            }

            const openUrls: Command = {
                key: 'open-urls',
                scope: GLOBAL_COMMAND_SCOPE,
                prefixes: ['open', 'visit'],
                resolver: (argument) => {
                    const results: CommandResultTemplate[] = (teamLogic.values.currentTeam?.app_urls ?? []).map(
                        (url: string) => ({
                            icon: LinkOutlined,
                            display: `Open ${url}`,
                            synonyms: [`Visit ${url}`],
                            executor: () => {
                                open(url)
                            },
                        })
                    )
                    if (argument && isURL(argument)) {
                        results.push({
                            icon: LinkOutlined,
                            display: `Open ${argument}`,
                            synonyms: [`Visit ${argument}`],
                            executor: () => {
                                open(argument)
                            },
                        })
                    }
                    results.push({
                        icon: LinkOutlined,
                        display: 'Open PostHog Docs',
                        synonyms: ['technical documentation'],
                        executor: () => {
                            open('https://posthog.com/docs')
                        },
                    })
                    return results
                },
            }

            const createPersonalApiKey: Command = {
                key: 'create-personal-api-key',
                scope: GLOBAL_COMMAND_SCOPE,
                resolver: {
                    icon: KeyOutlined,
                    display: 'Create Personal API Key',
                    executor: () => ({
                        instruction: 'Give your key a label',
                        icon: TagOutlined,
                        scope: 'Creating Personal API Key',
                        resolver: (argument) => {
                            if (argument?.length) {
                                return {
                                    icon: KeyOutlined,
                                    display: `Create Key "${argument}"`,
                                    executor: () => {
                                        personalAPIKeysLogic.actions.createKey(argument)
                                        push(urls.mySettings(), {}, 'personal-api-keys')
                                    },
                                }
                            }
                            return null
                        },
                    }),
                },
            }

            const createDashboard: Command = {
                key: 'create-dashboard',
                scope: GLOBAL_COMMAND_SCOPE,
                resolver: {
                    icon: FundOutlined,
                    display: 'Create Dashboard',
                    executor: () => ({
                        instruction: 'Name your new dashboard',
                        icon: TagOutlined,
                        scope: 'Creating Dashboard',
                        resolver: (argument) => {
                            if (argument?.length) {
                                return {
                                    icon: FundOutlined,
                                    display: `Create Dashboard "${argument}"`,
                                    executor: () => {
                                        dashboardsModel.actions.addDashboard({ name: argument, show: true })
                                    },
                                }
                            }
                            return null
                        },
                    }),
                },
            }

            const shareFeedback: Command = {
                key: 'share-feedback',
                scope: GLOBAL_COMMAND_SCOPE,
                resolver: {
                    icon: CommentOutlined,
                    display: 'Share Feedback',
                    synonyms: ['send opinion', 'ask question', 'message posthog', 'github issue'],
                    executor: () => ({
                        scope: 'Sharing Feedback',
                        resolver: [
                            {
                                display: 'Send Message Directly to PostHog',
                                icon: CommentOutlined,
                                executor: () => ({
                                    instruction: "What's on your mind?",
                                    icon: CommentOutlined,
                                    resolver: (argument) => ({
                                        icon: SendOutlined,
                                        display: 'Send',
                                        executor: !argument?.length
                                            ? undefined
                                            : () => {
                                                  posthog.capture('palette feedback', { message: argument })
                                                  return {
                                                      resolver: {
                                                          icon: CheckOutlined,
                                                          display: 'Message Sent!',
                                                          executor: true,
                                                      },
                                                  }
                                              },
                                    }),
                                }),
                            },
                            {
                                icon: VideoCameraOutlined,
                                display: 'Schedule Quick Call',
                                executor: () => {
                                    open('https://calendly.com/posthog-feedback')
                                },
                            },
                            {
                                icon: ExclamationCircleOutlined,
                                display: 'Create GitHub Issue',
                                executor: () => {
                                    open('https://github.com/PostHog/posthog/issues/new/choose')
                                },
                            },
                        ],
                    }),
                },
            }

            actions.registerCommand(goTo)
            actions.registerCommand(openUrls)
            actions.registerCommand(debugClickhouseQueries)
            actions.registerCommand(calculator)
            actions.registerCommand(createPersonalApiKey)
            actions.registerCommand(createDashboard)
            actions.registerCommand(shareFeedback)
        },
        beforeUnmount: () => {
            actions.deregisterCommand('go-to')
            actions.deregisterCommand('open-urls')
            actions.deregisterCommand('debug-clickhouse-queries')
            actions.deregisterCommand('calculator')
            actions.deregisterCommand('create-personal-api-key')
            actions.deregisterCommand('create-dashboard')
            actions.deregisterCommand('share-feedback')
        },
    }),
})