react#Key TypeScript Examples

The following examples show how to use react#Key. 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: react-utils.ts    From utopia with MIT License 7 votes vote down vote up
// Utility function to interact with react. Provides the following advantages:
  // - forces the use of a key
  // - by having key and children as separate properties, the attrs param has the correct shape for the component model
  // - it's shorter than createElement :p

  // DOM Elements
  static create<P extends HTMLAttributes<T>, T extends HTMLElement>(
    type: keyof ReactHTML,
    props?: { key: Key } & ClassAttributes<T> & P,
    ...children: ReactNode[]
  ): DetailedReactHTMLElement<P, T>
Example #2
Source File: react-utils.ts    From utopia with MIT License 6 votes vote down vote up
static create<P>(
    type: ComponentClass<P>,
    props?: { key: Key } & Attributes & P,
    ...children: ReactNode[]
  ): ReactElement<P>
Example #3
Source File: useChildrenEnterLeave.ts    From fe-foundation with Apache License 2.0 6 votes vote down vote up
/**
 * 检查元素是否存在
 * - 如果元素不存在,移除当前动画
 * - 如果元素存在,但没有挂载在文档中,移除当前动画与元素的信息
 */
function getDomNode(
    map: Map<Key, DomNodeInfo>,
    keys: Set<Key> | Map<Key, number>,
    key: Key,
    skipChildWithoutRef: boolean
): DomNodeInfo {
    let domNode = map.get(key);
    if (!domNode) {
        domNode = {};
        map.set(key, domNode);
    }

    if (!domNode.element) {
        if (skipChildWithoutRef) {
            map.delete(key);
            keys.delete(key);
        }
    } else if (!domNode.element.isConnected) {
        map.delete(key);
        keys.delete(key);
    }

    return domNode;
}
Example #4
Source File: react-utils.ts    From utopia with MIT License 6 votes vote down vote up
static create<P, T extends Component<P, ComponentState>, C extends ComponentClass<P>>(
    type: ClassType<P, T, C>,
    props?: { key: Key } & ClassAttributes<T> & P,
    ...children: ReactNode[]
  ): CElement<P, T>
Example #5
Source File: react-utils.ts    From utopia with MIT License 6 votes vote down vote up
static create<P extends SVGAttributes<T>, T extends SVGElement>(
    type: keyof ReactSVG,
    props?: { key: Key } & ClassAttributes<T> & P,
    ...children: ReactNode[]
  ): ReactSVGElement
Example #6
Source File: react-utils.ts    From utopia with MIT License 6 votes vote down vote up
static create<P extends DOMAttributes<T>, T extends HTMLElement>(
    type: string,
    props?: { key: Key } & ClassAttributes<T> & P,
    ...children: ReactNode[]
  ): DOMElement<P, T>
Example #7
Source File: react-utils.ts    From utopia with MIT License 6 votes vote down vote up
// Custom components
  static create<P>(
    type: FC<React.PropsWithChildren<P>>,
    props?: { key: Key } & Attributes & P,
    ...children: ReactNode[]
  ): FunctionComponentElement<P>
Example #8
Source File: react-utils.ts    From utopia with MIT License 6 votes vote down vote up
static create<P>(
    type: ClassType<P, ClassicComponent<P, ComponentState>, ClassicComponentClass<P>>,
    props?: { key: Key } & ClassAttributes<ClassicComponent<P, ComponentState>> & P,
    ...children: ReactNode[]
  ): CElement<P, ClassicComponent<P, ComponentState>>
Example #9
Source File: useSelectionState.ts    From use-platform with MIT License 5 votes vote down vote up
/**
 * Manages state for single and multiple selection.
 *
 * @example
 * const Component = (props) => {
 *   const selectionState = useSelectionState({
 *      selectionMode: 'multiple',
 *      defaultSelectedKeys: ['a', 'b'],
 *      onSelectionChange: (keys) => {
 *         // ...
 *      },
 *   });
 * })
 */
export function useSelectionState(props: UseSelectionStateProps): UseSelectionStateResult {
  const {
    defaultSelectedKeys,
    onSelectionChange,
    selectionMode,
    disallowEmptySelection = false,
  } = props
  const [selectedKeys, setSelectedKeys] = useState<Set<Key>>(() => new Set(defaultSelectedKeys))
  const onSelectionChangeRef = useRef(onSelectionChange)

  const select = useCallback(
    (key: Key) => {
      const isExists = selectedKeys.has(key)

      if (
        selectionMode === 'none' ||
        (isExists && disallowEmptySelection && selectedKeys.size === 1)
      ) {
        return
      }

      const keys = new Set(selectedKeys)
      if (isExists) {
        keys.delete(key)
      } else {
        if (selectionMode === 'single') {
          keys.clear()
        }

        keys.add(key)
      }

      setSelectedKeys(keys)
      onSelectionChangeRef.current?.(keys)
    },
    [selectedKeys, selectionMode, disallowEmptySelection],
  )

  const isSelected = useCallback((key: Key) => selectedKeys.has(key), [selectedKeys])

  return {
    selectedKeys,
    isSelected,
    select,
  }
}
Example #10
Source File: ListInput.tsx    From project-loved-web with MIT License 5 votes vote down vote up
export default function ListInput<T, VT extends Key>(props: ListInputProps<T, VT>) {
  const [inputKeys, setInputKeys] = useState<number[]>([]);
  const [items, setItems] = useState(props.items);

  useEffect(() => {
    setInputKeys([]);
    setItems(props.items);
  }, [props.items]);

  const addInput = () => setInputKeys((prev) => prev.concat(nextKey++));
  const removeInput = (key: number) => setInputKeys((prev) => prev.filter((i) => i !== key));
  const removeItem = (item: T) => setItems((prev) => prev.filter((i) => i !== item));

  return (
    <>
      {items.map((item) => (
        <div key={props.itemValue(item)} className='list-input-row'>
          {props.itemRender(item)}
          <input
            type='hidden'
            name={props.name}
            value={props.itemValue(item)}
            data-value-type={props.valueType}
            data-array
          />{' '}
          <button type='button' className='fake-a error' onClick={() => removeItem(item)}>
            [-]
          </button>
        </div>
      ))}
      {inputKeys.map((inputKey) => (
        <div key={inputKey} className='list-input-row'>
          <input
            type={props.type}
            name={props.name}
            placeholder={props.placeholder}
            data-value-type={props.valueType}
            data-array
          />{' '}
          <button type='button' className='fake-a error' onClick={() => removeInput(inputKey)}>
            [-]
          </button>
        </div>
      ))}
      <button type='button' className='fake-a success' onClick={addInput}>
        [+]
      </button>
    </>
  );
}
Example #11
Source File: useChildrenEnterLeave.ts    From fe-foundation with Apache License 2.0 4 votes vote down vote up
export function useChildrenEnterLeave<T>(
    children: ReactNode,
    options: IUseChildrenEnterLeaveOptions<T> = {}
): [ReactNode, () => () => void] {
    const {
        childRefKey = 'ref',
        disabled,
        duration,
        enterChildProps,
        ignoreInitialRender,
        leaveChildProps,
        moveChildProps,
        onFrameAnimationEnd,
        skipChildWithoutRef = true,
        timingFunction,
        updateAfterAnimation = true
    } = options;

    const {
        enterTick,
        leaveTick,
        moveTick
    } = getEnterLeaveMoveTick(options);

    const getEnterChildProps = isNotPropsGetter(enterChildProps)
        ? () => enterChildProps
        : enterChildProps;
    const getLeaveChildProps = isNotPropsGetter(leaveChildProps)
        ? () => leaveChildProps
        : leaveChildProps;
    const getMoveChildProps = isNotPropsGetter(moveChildProps)
        ? () => moveChildProps
        : moveChildProps;

    const forceUpdate = useUpdate();
    const initialRender = useRef(true);

    const leavingKeys = useSingleton(() => new Set<Key>());
    const enteringKeys = useSingleton(() => new Set<Key>());
    const movingKeys = useSingleton(() => new Map<Key, number>());

    const childMap = new Map<Key, [number, ReactElement]>();
    const prevChildMap = usePrevProp(childMap, () => false)[1];
    const domNodeMap = useSingleton(() => new Map<Key, DomNodeInfo>());

    const handleFrameAnimationEnd = useRefCallback(() => {
        onFrameAnimationEnd?.();
        if (updateAfterAnimation) {
            forceUpdate();
        }
    });

    useEffect(() => {
        initialRender.current = false;
    }, []);

    if (disabled || (ignoreInitialRender && initialRender.current)) {
        React.Children.toArray(children).forEach((child, index) => {
            if (React.isValidElement(child) && child.key) {
                childMap.set(child.key, [index, child]);
            }
        });
        return [children, () => () => void 0];
    }

    /** DIFF CHILDREN START */

    const childRef = (key: Key) => (element: HTMLElement | null): void => {
        if (element) {
            domNodeMap.set(key, {...domNodeMap.get(key), element});
        } else if (!enteringKeys.has(key) && !leavingKeys.has(key) && !movingKeys.has(key)) {
            // 避免 children 更新时,正在进行的帧动画中断
            domNodeMap.delete(key);
        }
    };

    const handleClone = (child: ReactNode, index: number): ReactNode => {
        if (!React.isValidElement(child) || !child.key) {
            return child;
        }

        const key = child.key;
        const exists = prevChildMap !== childMap && prevChildMap.get(key);
        const moved = exists && exists[0] !== index;
        const rewriteProps = !exists
            ? getEnterChildProps(child.props, key)
            : moved ? getMoveChildProps(child.props, key, exists[0] - index) : {};

        if (exists) {
            // prevChildMap 最终只会剩下需要离场的子元素
            prevChildMap.delete(key);
            // 保存移动的 index 数量
            moved && movingKeys.set(key, exists[0] - index);
        } else {
            leavingKeys.delete(key);
            enteringKeys.add(key);
        }

        // if (__DEV__ && childMap.has(key)) {
        //     console.warn(`[useChildrenEnterLeave] duplicate key found '${key}'.`);
        // }

        const refKey = typeof childRefKey === 'function' ? childRefKey(child) : childRefKey;

        // @see https://reactjs.org/docs/react-api.html#cloneelement
        const reactNode = React.cloneElement(child, {
            ...rewriteProps,
            ...(refKey ? {[refKey]: childRef(key)} : {})
        });

        childMap.set(key, [index, reactNode]);

        return reactNode;
    };

    const clonedChildren = React.Children.toArray(children).map(handleClone);

    if (prevChildMap !== childMap) {
        prevChildMap.forEach(([index, child], key) => {
            leavingKeys.add(key);
            enteringKeys.delete(key);

            const refKey = typeof childRefKey === 'function' ? childRefKey(child) : childRefKey;
            const reactNode = React.cloneElement(child, {
                ...getLeaveChildProps(child.props, key),
                ...(refKey ? {[refKey]: childRef(key)} : {})
            });

            clonedChildren.splice(index, 0, reactNode);
        });
    }

    /** DIFF CHILDREN END */

    if (!leavingKeys.size && !enteringKeys.size && !movingKeys.size) {
        return [children, () => () => void 0];
    }

    if (duration === undefined || (!enterTick && !leaveTick && !moveTick)) {
        return [children, () => () => void 0];
    }

    const durations = getDuration(duration);
    const timings = getTimingFunction(timingFunction);

    const tick: FrameAnimationTick = (now: number = performance.now()) => {
        if (enterTick) {
            enteringKeys.forEach(key => {
                const domNode = getDomNode(domNodeMap, enteringKeys, key, skipChildWithoutRef);
                const {percent, finished} = getPercent(domNode, now, durations, timings, 'enter');
                if (finished) {
                    enteringKeys.delete(key);
                    if (!domNode.element) {
                        domNodeMap.delete(key);
                    }
                }
                if (domNode.element) {
                    domNode.memo = enterTick(domNode.element, percent, domNode.memo, key);
                }
            });
        } else {
            enteringKeys.clear();
        }

        if (leaveTick) {
            leavingKeys.forEach(key => {
                const domNode = getDomNode(domNodeMap, leavingKeys, key, skipChildWithoutRef);
                const {percent, finished} = getPercent(domNode, now, durations, timings, 'leave');
                if (finished) {
                    leavingKeys.delete(key);
                    if (!domNode.element) {
                        domNodeMap.delete(key);
                    }
                }
                if (domNode.element) {
                    domNode.memo = leaveTick(domNode.element, percent, domNode.memo, key);
                }
            });
        } else {
            leavingKeys.clear();
        }

        if (moveTick) {
            movingKeys.forEach((moved, key) => {
                const domNode = getDomNode(domNodeMap, movingKeys, key, skipChildWithoutRef);
                const {percent, finished} = getPercent(domNode, now, durations, timings, 'move');
                if (finished) {
                    movingKeys.delete(key);
                    if (!domNode.element) {
                        domNodeMap.delete(key);
                    }
                }
                if (domNode.element) {
                    domNode.memo = moveTick(domNode.element, percent, moved, domNode.memo, key);
                }
            });
        } else {
            movingKeys.clear();
        }

        if (!enteringKeys.size && !leavingKeys.size && !movingKeys.size) {
            tick.frame = undefined;
            handleFrameAnimationEnd();
        } else {
            tick.frame = requestAnimationFrame(tick);
        }
    };

    const start = (): () => void => {
        tick.frame = requestAnimationFrame(tick);
        return () => {
            tick.frame !== undefined && cancelAnimationFrame(tick.frame);
        };
    };

    return [clonedChildren, start];
}
Example #12
Source File: index.tsx    From ql with MIT License 4 votes vote down vote up
Log = () => {
  const [width, setWidth] = useState('100%');
  const [marginLeft, setMarginLeft] = useState(0);
  const [marginTop, setMarginTop] = useState(-72);
  const [title, setTitle] = useState('请选择日志文件');
  const [value, setValue] = useState('请选择日志文件');
  const [select, setSelect] = useState();
  const [data, setData] = useState<any[]>([]);
  const [filterData, setFilterData] = useState<any[]>([]);
  const [loading, setLoading] = useState(false);
  const [isPhone, setIsPhone] = useState(false);

  const getConfig = () => {
    request.get(`${config.apiPrefix}logs`).then((data) => {
      const result = formatData(data.dirs) as any;
      setData(result);
      setFilterData(result);
    });
  };

  const formatData = (tree: any[]) => {
    return tree.map((x) => {
      x.title = x.name;
      x.value = x.name;
      x.disabled = x.isDir;
      x.key = x.name;
      x.children = x.files.map((y: string) => ({
        title: y,
        value: `${x.name}/${y}`,
        key: `${x.name}/${y}`,
        parent: x.name,
        isLeaf: true,
      }));
      return x;
    });
  };

  const getLog = (node: any) => {
    setLoading(true);
    request
      .get(`${config.apiPrefix}logs/${node.value}`)
      .then((data) => {
        setValue(data.data);
      })
      .finally(() => setLoading(false));
  };

  const onSelect = (value: any, node: any) => {
    setSelect(value);
    setTitle(node.parent || node.value);
    getLog(node);
  };

  const onTreeSelect = useCallback((keys: Key[], e: any) => {
    onSelect(keys[0], e.node);
  }, []);

  const onSearch = useCallback(
    (e) => {
      const keyword = e.target.value;
      const { tree } = getFilterData(keyword.toLocaleLowerCase(), data);
      setFilterData(tree);
    },
    [data, setFilterData],
  );

  useEffect(() => {
    if (document.body.clientWidth < 768) {
      setWidth('auto');
      setMarginLeft(0);
      setMarginTop(0);
      setIsPhone(true);
    } else {
      setWidth('100%');
      setMarginLeft(0);
      setMarginTop(-72);
      setIsPhone(false);
    }
    getConfig();
  }, []);

  return (
    <PageContainer
      className="ql-container-wrapper log-wrapper"
      title={title}
      extra={
        isPhone && [
          <TreeSelect
            className="log-select"
            value={select}
            dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
            treeData={data}
            placeholder="请选择日志文件"
            showSearch
            key="value"
            onSelect={onSelect}
          />,
        ]
      }
      header={{
        style: {
          padding: '4px 16px 4px 15px',
          position: 'sticky',
          top: 0,
          left: 0,
          zIndex: 20,
          marginTop,
          width,
          marginLeft,
        },
      }}
    >
      <div className={`${styles['log-container']}`}>
        {!isPhone && (
          <div className={styles['left-tree-container']}>
            <Input.Search
              className={styles['left-tree-search']}
              onChange={onSearch}
            ></Input.Search>
            <div className={styles['left-tree-scroller']}>
              <Tree
                className={styles['left-tree']}
                treeData={filterData}
                onSelect={onTreeSelect}
              ></Tree>
            </div>
          </div>
        )}
        <CodeMirror
          value={value}
          options={{
            lineNumbers: true,
            lineWrapping: true,
            styleActiveLine: true,
            matchBrackets: true,
            mode: 'shell',
            readOnly: true,
          }}
          onBeforeChange={(editor, data, value) => {
            setValue(value);
          }}
          onChange={(editor, data, value) => {}}
        />
      </div>
    </PageContainer>
  );
}
Example #13
Source File: MemberTable.tsx    From datart with Apache License 2.0 4 votes vote down vote up
MemberTable = memo(
  ({ loading, dataSource, onAdd, onChange }: MemberTableProps) => {
    const [keywords, setKeywords] = useState('');
    const [selectedRowKeys, setSelectedRowKeys] = useState<Key[]>([]);
    const t = useI18NPrefix('member.roleDetail');
    const tg = useI18NPrefix('global');

    const filteredSource = useMemo(
      () =>
        dataSource.filter(
          ({ username, email, name }) =>
            username.toLowerCase().includes(keywords) ||
            email.toLowerCase().includes(keywords) ||
            (name && name.toLowerCase().includes(keywords)),
        ),
      [dataSource, keywords],
    );

    const debouncedSearch = useMemo(() => {
      const search = e => {
        setKeywords(e.target.value);
      };
      return debounce(search, DEFAULT_DEBOUNCE_WAIT);
    }, []);

    const removeMember = useCallback(
      id => () => {
        onChange(dataSource.filter(d => d.id !== id));
        setSelectedRowKeys([]);
      },
      [dataSource, onChange],
    );

    const removeSelectedMember = useCallback(() => {
      onChange(dataSource.filter(d => !selectedRowKeys.includes(d.id)));
      setSelectedRowKeys([]);
    }, [dataSource, selectedRowKeys, onChange]);

    const columns = useMemo(
      () => [
        { dataIndex: 'username', title: t('username') },
        { dataIndex: 'email', title: t('email') },
        { dataIndex: 'name', title: t('name') },
        {
          title: tg('title.action'),
          width: 80,
          align: 'center' as const,
          render: (_, record) => (
            <Action onClick={removeMember(record.id)}>{t('remove')}</Action>
          ),
        },
      ],
      [removeMember, t, tg],
    );

    return (
      <>
        <Toolbar>
          <Col span={4}>
            <Button
              type="link"
              icon={<PlusOutlined />}
              className="btn"
              onClick={onAdd}
            >
              {t('addMember')}
            </Button>
          </Col>
          <Col span={14}>
            {selectedRowKeys.length > 0 && (
              <Button
                type="link"
                icon={<DeleteOutlined />}
                className="btn"
                onClick={removeSelectedMember}
              >
                {t('deleteAll')}
              </Button>
            )}
          </Col>
          <Col span={6}>
            <Input
              placeholder={t('searchMember')}
              prefix={<SearchOutlined className="icon" />}
              bordered={false}
              onChange={debouncedSearch}
            />
          </Col>
        </Toolbar>
        <Table
          rowKey="id"
          dataSource={filteredSource}
          columns={columns}
          loading={loading}
          size="small"
          rowSelection={{ selectedRowKeys, onChange: setSelectedRowKeys }}
          bordered
        />
      </>
    );
  },
)
Example #14
Source File: index.tsx    From datart with Apache License 2.0 4 votes vote down vote up
SubjectForm = memo(
  ({
    scope,
    editingVariable,
    loading,
    rowPermissions,
    afterClose,
    onSave,
    ...modalProps
  }: SubjectFormProps) => {
    const [tab, setTab] = useState('role');
    const [selectedRoleRowKeys, setSelectedRoleRowKeys] = useState<Key[]>([]);
    const [selectedMemberRowKeys, setSelectedMemberRowKeys] = useState<Key[]>(
      [],
    );
    const [roleRowPermissionSubjects, setRoleRowPermissionSubjects] = useState<
      undefined | RowPermissionSubject[]
    >(void 0);
    const [memberRowPermissionSubjects, setMemberRowPermissionSubjects] =
      useState<undefined | RowPermissionSubject[]>(void 0);
    const roles = useSelector(selectRoles);
    const members = useSelector(selectMembers);
    const roleListLoading = useSelector(selectRoleListLoading);
    const memberListLoading = useSelector(selectMemberListLoading);
    const t = useI18NPrefix('variable');

    useEffect(() => {
      if (editingVariable && rowPermissions && roles) {
        const roleRowPermissions = rowPermissions.filter(
          ({ subjectType }) => subjectType === SubjectTypes.Role,
        );
        const rowPermissionSubjects: RowPermissionSubject[] = [];
        const selectedRowKeys: string[] = [];
        roles.forEach(({ id, name }) => {
          const permission = roleRowPermissions.find(
            ({ subjectId }) => subjectId === id,
          );
          rowPermissionSubjects.push({
            id,
            name,
            type: SubjectTypes.Role,
            useDefaultValue: permission ? permission.useDefaultValue : true,
            value: permission?.value
              ? editingVariable.valueType === VariableValueTypes.Date
                ? permission.value.map(str => moment(str))
                : permission.value
              : void 0,
          });
          if (permission) {
            selectedRowKeys.push(id);
          }
        });
        setRoleRowPermissionSubjects(rowPermissionSubjects);
        setSelectedRoleRowKeys(selectedRowKeys);
      }
    }, [editingVariable, rowPermissions, roles]);

    useEffect(() => {
      if (editingVariable && rowPermissions && members) {
        const memberRowPermissions = rowPermissions.filter(
          ({ subjectType }) => subjectType === SubjectTypes.User,
        );
        const rowPermissionSubjects: RowPermissionSubject[] = [];
        const selectedRowKeys: string[] = [];
        members.forEach(({ id, name, username, email }) => {
          const permission = memberRowPermissions.find(
            ({ subjectId }) => subjectId === id,
          );
          rowPermissionSubjects.push({
            id,
            name: name ? `${name}(${username})` : username,
            email,
            type: SubjectTypes.User,
            useDefaultValue: permission ? permission.useDefaultValue : true,
            value: permission?.value
              ? editingVariable.valueType === VariableValueTypes.Date
                ? permission.value.map(str => moment(str))
                : permission.value
              : void 0,
          });
          if (permission) {
            selectedRowKeys.push(id);
          }
        });
        setMemberRowPermissionSubjects(rowPermissionSubjects);
        setSelectedMemberRowKeys(selectedRowKeys);
      }
    }, [editingVariable, rowPermissions, members]);

    const save = useCallback(() => {
      const roleRowPermissions: RowPermission[] = selectedRoleRowKeys.map(
        key => {
          const permission = roleRowPermissionSubjects?.find(
            rps => rps.id === key,
          )!;
          return {
            id: uuidv4(),
            variableId: editingVariable!.id,
            subjectId: key as string,
            subjectType: SubjectTypes.Role,
            useDefaultValue: permission.useDefaultValue,
            value: permission.value,
          };
        },
      );
      const roleMemberPermissions: RowPermission[] = selectedMemberRowKeys.map(
        key => {
          const permission = memberRowPermissionSubjects?.find(
            rps => rps.id === key,
          )!;
          return {
            id: uuidv4(),
            variableId: editingVariable!.id,
            subjectId: key as string,
            subjectType: SubjectTypes.User,
            useDefaultValue: permission.useDefaultValue,
            value: permission.value,
          };
        },
      );
      onSave(roleRowPermissions.concat(roleMemberPermissions));
    }, [
      editingVariable,
      roleRowPermissionSubjects,
      memberRowPermissionSubjects,
      selectedRoleRowKeys,
      selectedMemberRowKeys,
      onSave,
    ]);

    const onAfterClose = useCallback(() => {
      setSelectedRoleRowKeys([]);
      setSelectedMemberRowKeys([]);
      afterClose && afterClose();
    }, [afterClose]);

    return (
      <StyledModal
        {...modalProps}
        width={768}
        title={
          scope === VariableScopes.Public ? (
            <>
              <StyledTabs defaultActiveKey={tab} onChange={setTab}>
                <Tabs.TabPane key="role" tab={t('relatedRole')} />
                <Tabs.TabPane key="member" tab={t('relatedMember')} />
              </StyledTabs>
            </>
          ) : (
            t('relatedRole')
          )
        }
        onOk={save}
        afterClose={onAfterClose}
        destroyOnClose
      >
        <RowPermissionTable
          type="role"
          visible={tab === 'role'}
          editingVariable={editingVariable}
          loading={!!loading}
          listLoading={roleListLoading}
          selectedRowKeys={selectedRoleRowKeys}
          rowPermissionSubjects={roleRowPermissionSubjects}
          onSelectedRowKeyChange={setSelectedRoleRowKeys}
          onRowPermissionSubjectChange={setRoleRowPermissionSubjects}
        />
        {scope === VariableScopes.Public && (
          <RowPermissionTable
            type="member"
            visible={tab === 'member'}
            editingVariable={editingVariable}
            loading={!!loading}
            listLoading={memberListLoading}
            selectedRowKeys={selectedMemberRowKeys}
            rowPermissionSubjects={memberRowPermissionSubjects}
            onSelectedRowKeyChange={setSelectedMemberRowKeys}
            onRowPermissionSubjectChange={setMemberRowPermissionSubjects}
          />
        )}
      </StyledModal>
    );
  },
)
Example #15
Source File: index.tsx    From datart with Apache License 2.0 4 votes vote down vote up
export function VariablePage() {
  useMemberSlice();
  useVariableSlice();
  const [formType, setFormType] = useState(CommonFormTypes.Add);
  const [formVisible, setFormVisible] = useState(false);
  const [editingVariable, setEditingVariable] = useState<undefined | Variable>(
    void 0,
  );
  const [rowPermissions, setRowPermissions] = useState<
    undefined | RowPermission[]
  >(void 0);
  const [rowPermissionLoading, setRowPermissionLoading] = useState(false);
  const [subjectFormVisible, setSubjectFormVisible] = useState(false);
  const [selectedRowKeys, setSelectedRowKeys] = useState<Key[]>([]);
  const [updateRowPermissionLoading, setUpdateRowPermissionLoading] =
    useState(false);
  const dispatch = useDispatch();
  const variables = useSelector(selectVariables);
  const listLoading = useSelector(selectVariableListLoading);
  const saveLoading = useSelector(selectSaveVariableLoading);
  const deleteVariablesLoading = useSelector(selectDeleteVariablesLoading);
  const orgId = useSelector(selectOrgId);
  const t = useI18NPrefix('variable');
  const tg = useI18NPrefix('global');

  useEffect(() => {
    dispatch(getVariables(orgId));
    dispatch(getMembers(orgId));
    dispatch(getRoles(orgId));
  }, [dispatch, orgId]);

  const showAddForm = useCallback(() => {
    setFormType(CommonFormTypes.Add);
    setFormVisible(true);
  }, []);

  const hideForm = useCallback(() => {
    setFormVisible(false);
  }, []);

  const afterFormClose = useCallback(() => {
    setEditingVariable(void 0);
  }, []);

  const showEditForm = useCallback(
    id => () => {
      setEditingVariable(variables.find(v => v.id === id));
      setFormType(CommonFormTypes.Edit);
      setFormVisible(true);
    },
    [variables],
  );

  const showSubjectForm = useCallback(
    id => async () => {
      const variable = variables.find(v => v.id === id)!;
      setEditingVariable(variable);
      setSubjectFormVisible(true);

      try {
        setRowPermissionLoading(true);
        const { data } = await request<RowPermissionRaw[]>(
          `/variables/value?variableId=${id}`,
        );
        setRowPermissions(
          data.map(d => ({
            ...d,
            value: d.value && JSON.parse(d.value),
          })),
        );
      } catch (error) {
        errorHandle(error);
        throw error;
      } finally {
        setRowPermissionLoading(false);
      }
    },
    [variables],
  );

  const hideSubjectForm = useCallback(() => {
    setSubjectFormVisible(false);
  }, []);

  const del = useCallback(
    id => () => {
      dispatch(
        deleteVariable({
          ids: [id],
          resolve: () => {
            message.success(tg('operation.deleteSuccess'));
          },
        }),
      );
    },
    [dispatch, tg],
  );

  const delSelectedVariables = useCallback(() => {
    dispatch(
      deleteVariable({
        ids: selectedRowKeys as string[],
        resolve: () => {
          message.success(tg('operation.deleteSuccess'));
          setSelectedRowKeys([]);
        },
      }),
    );
  }, [dispatch, selectedRowKeys, tg]);

  const save = useCallback(
    (values: VariableFormModel) => {
      let defaultValue: any = values.defaultValue;
      if (values.valueType === VariableValueTypes.Date && !values.expression) {
        defaultValue = values.defaultValue.map(d =>
          (d as Moment).format(TIME_FORMATTER),
        );
      }

      try {
        if (defaultValue !== void 0 && defaultValue !== null) {
          defaultValue = JSON.stringify(defaultValue);
        }
      } catch (error) {
        errorHandle(error);
        throw error;
      }

      if (formType === CommonFormTypes.Add) {
        dispatch(
          addVariable({
            variable: { ...values, orgId, defaultValue },
            resolve: () => {
              hideForm();
            },
          }),
        );
      } else {
        dispatch(
          editVariable({
            variable: { ...editingVariable!, ...values, defaultValue },
            resolve: () => {
              hideForm();
              message.success(tg('operation.updateSuccess'));
            },
          }),
        );
      }
    },
    [dispatch, formType, orgId, editingVariable, hideForm, tg],
  );

  const saveRelations = useCallback(
    async (changedRowPermissions: RowPermission[]) => {
      let changedRowPermissionsRaw = changedRowPermissions.map(cr => ({
        ...cr,
        value:
          cr.value &&
          (editingVariable?.valueType === VariableValueTypes.Date
            ? cr.value.map(m => (m as Moment).format(TIME_FORMATTER))
            : cr.value),
      }));

      if (rowPermissions) {
        const { created, updated, deleted } = getDiffParams(
          [...rowPermissions],
          changedRowPermissionsRaw,
          (oe, ce) =>
            oe.subjectId === ce.subjectId && oe.variableId === ce.variableId,
          (oe, ce) =>
            oe.useDefaultValue !== ce.useDefaultValue || oe.value !== ce.value,
        );

        if (created.length > 0 || updated.length > 0 || deleted.length > 0) {
          try {
            setUpdateRowPermissionLoading(true);
            await request<null>({
              url: '/variables/rel',
              method: 'PUT',
              data: {
                relToCreate: created.map(r => ({
                  ...r,
                  value: JSON.stringify(r.value),
                })),
                relToUpdate: updated.map(r => ({
                  ...r,
                  value: JSON.stringify(r.value),
                })),
                relToDelete: deleted.map(({ id }) => id),
              },
            });
            message.success(tg('operation.updateSuccess'));
            setSubjectFormVisible(false);
          } catch (error) {
            errorHandle(error);
            throw error;
          } finally {
            setUpdateRowPermissionLoading(false);
          }
        } else {
          setSubjectFormVisible(false);
        }
      }
    },
    [rowPermissions, editingVariable, tg],
  );

  const columns: TableColumnProps<VariableViewModel>[] = useMemo(
    () => [
      { dataIndex: 'name', title: t('name') },
      { dataIndex: 'label', title: t('label') },
      {
        dataIndex: 'type',
        title: t('type'),
        render: (_, record) => (
          <Tag
            color={record.type === VariableTypes.Permission ? WARNING : INFO}
          >
            {t(`variableType.${record.type.toLowerCase()}`)}
          </Tag>
        ),
      },
      {
        dataIndex: 'valueType',
        title: t('valueType'),
        render: (_, record) =>
          t(`variableValueType.${record.valueType.toLowerCase()}`),
      },
      {
        title: tg('title.action'),
        align: 'center',
        width: 140,
        render: (_, record) => (
          <Actions>
            {record.type === VariableTypes.Permission && (
              <Tooltip title={t('related')}>
                <Button
                  type="link"
                  icon={<TeamOutlined />}
                  onClick={showSubjectForm(record.id)}
                />
              </Tooltip>
            )}
            <Tooltip title={tg('button.edit')}>
              <Button
                type="link"
                icon={<EditOutlined />}
                onClick={showEditForm(record.id)}
              />
            </Tooltip>
            <Tooltip title={tg('button.delete')}>
              <Popconfirm
                title={tg('operation.deleteConfirm')}
                onConfirm={del(record.id)}
              >
                <Button type="link" icon={<DeleteOutlined />} />
              </Popconfirm>
            </Tooltip>
          </Actions>
        ),
      },
    ],
    [del, showEditForm, showSubjectForm, t, tg],
  );

  const pagination = useMemo(
    () => ({ pageSize: 20, pageSizeOptions: ['20', '50', '100'] }),
    [],
  );

  return (
    <Wrapper>
      <Card>
        <TableHeader>
          <h3>{t('title')}</h3>
          <Toolbar>
            {selectedRowKeys.length > 0 && (
              <Popconfirm
                title={t('deleteAllConfirm')}
                onConfirm={delSelectedVariables}
              >
                <Button
                  icon={<DeleteOutlined />}
                  loading={deleteVariablesLoading}
                >
                  {t('deleteAll')}
                </Button>
              </Popconfirm>
            )}
            <Button
              icon={<PlusOutlined />}
              type="primary"
              onClick={showAddForm}
            >
              {tg('button.create')}
            </Button>
          </Toolbar>
        </TableHeader>
        <Table
          rowKey="id"
          size="small"
          dataSource={variables}
          columns={columns}
          loading={listLoading}
          rowSelection={{ selectedRowKeys, onChange: setSelectedRowKeys }}
          pagination={pagination}
        />
        <VariableForm
          scope={VariableScopes.Public}
          orgId={orgId}
          editingVariable={editingVariable}
          visible={formVisible}
          title={t('public')}
          type={formType}
          confirmLoading={saveLoading}
          onSave={save}
          onCancel={hideForm}
          afterClose={afterFormClose}
          keyboard={false}
          maskClosable={false}
        />
        <SubjectForm
          scope={VariableScopes.Public}
          editingVariable={editingVariable}
          loading={rowPermissionLoading}
          rowPermissions={rowPermissions}
          visible={subjectFormVisible}
          confirmLoading={updateRowPermissionLoading}
          onSave={saveRelations}
          onCancel={hideSubjectForm}
          afterClose={afterFormClose}
          keyboard={false}
          maskClosable={false}
        />
      </Card>
    </Wrapper>
  );
}
Example #16
Source File: CollectionBuilder.test.tsx    From use-platform with MIT License 4 votes vote down vote up
describe('CollectionBuilder', () => {
  test('should throw error for non-collection component', () => {
    const builder = new CollectionBuilder()

    expect(() => {
      // @ts-expect-error
      Array.from(builder.build({ children: 'string' }))
    }).toThrowError('Unknown element')

    expect(() => {
      Array.from(builder.build({ children: <>Fragment</> }))
    }).toThrowError('Unknown element')

    expect(() => {
      Array.from(builder.build({ children: <div>container</div> }))
    }).toThrowError('Unknown element')
  })

  test('should generate key for item collection', () => {
    const builder = new CollectionBuilder()

    /* eslint-disable react/jsx-key */
    const nodes = builder.build({
      children: [
        <CollectionItem type="item">item 1</CollectionItem>,
        <CollectionItem type="item">item 2</CollectionItem>,
        <CollectionItem type="item">item 3</CollectionItem>,
      ],
    })
    /* eslint-enable  */

    const keys: Key[] = []
    forEach(nodes, (node) => keys.push(node.key))

    expect(keys).toEqual(['$.0', '$.1', '$.2'])
  })

  test('should use custom key for item collection', () => {
    const builder = new CollectionBuilder()

    /* eslint-disable react/jsx-key */
    const nodes = builder.build({
      children: [
        <CollectionItem type="item" key="foo">
          item 1
        </CollectionItem>,
        <CollectionItem type="item">item 2</CollectionItem>,
        <CollectionItem type="item" key="baz">
          item 3
        </CollectionItem>,
      ],
    })
    /* eslint-enable  */

    const keys: Key[] = []
    forEach(nodes, (node) => keys.push(node.key))

    expect(keys).toEqual(['foo', '$.1', 'baz'])
  })

  test('should generate key for nested item', () => {
    const builder = new CollectionBuilder()

    /* eslint-disable react/jsx-key */
    const nodes = builder.build({
      children: [
        <CollectionItem type="item" content="parent">
          <CollectionItem type="item">child</CollectionItem>
        </CollectionItem>,
        <CollectionItem type="item">item 2</CollectionItem>,
        <CollectionItem type="item">item 3</CollectionItem>,
      ],
    })
    /* eslint-enable  */

    const keys: Key[] = []
    forEach(nodes, (node) => keys.push(node.key))

    expect(keys).toEqual(['$.0', '$.0.0', '$.1', '$.2'])
  })

  test('should use custom key for nested item', () => {
    const builder = new CollectionBuilder()

    /* eslint-disable react/jsx-key */
    const nodes = builder.build({
      children: [
        <CollectionItem type="item" content="parent">
          <CollectionItem type="item" key="child">
            child
          </CollectionItem>
        </CollectionItem>,
      ],
    })
    /* eslint-enable  */

    const keys: Key[] = []
    forEach(nodes, (node) => keys.push(node.key))

    expect(keys).toEqual(['$.0', 'child'])
  })

  test('should throw error if children type is incorrect', () => {
    const builder = new CollectionBuilder()
    const nodes = builder.build({
      children: (
        <CollectionItem type="section" childType="item" content="Section">
          <CollectionItem type="section">Another section</CollectionItem>
        </CollectionItem>
      ),
    })

    expect(() => {
      forEach(nodes, () => null)
    }).toThrowError('Invalid node type. Expected: "item", actual: "section"')
  })

  test('should skip invalid collection items', () => {
    const builder = new CollectionBuilder()

    const nodes = builder.build({
      children: <CollectionItem>Item</CollectionItem>,
    })

    expect(Array.from(nodes)).toHaveLength(0)
  })
})
Example #17
Source File: UsersTable.tsx    From one-platform with MIT License 4 votes vote down vote up
UsersTable = ( { admin, db, appId, forceRefreshApp }: UsersTableProps) => {
  const [ isUserDataLoading, setIsUserDataLoading ] = useState(true);
  const [ columns ] = useState(['Name', 'Permission', 'Action']);
  const [ rows, setRows ] = useState( [ [] ] as any[][] );
  const [ isModalOpen, setIsModalOpen ] = useState<boolean>(false);
  const [ isRemovingUser, setIsRemovingUser ] = useState<boolean>( false );
  const [ rhatUUIDtoDel, setRhatUUIDtoDel ] = useState<string>( '' );

  useEffect(() => {
    const queries = getUserQueries(admin, db);
    const joinedQuery = queries.join();
    fetch(process.env.REACT_APP_API_GATEWAY, {
      method: `POST`,
      headers: {
        Authorization: `Bearer ${window.OpAuthHelper.jwtToken}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ query: `query{${joinedQuery}}` }),
    })
      .then((res: any) => res.json())
      .then( ( res: any ) => {
        let memberRows = [[]] as any[][];
        if ( !res.errors ) {
          memberRows = getMemberRows(res, admin, db);
        }
        else {
          window.OpNotification?.danger({
            subject: 'An error occurred when getting user details',
            body: res.errors[0].message,
          });
        }
        setIsUserDataLoading(false);
        setRows(memberRows);
      });
  }, [admin, db]);

  const handleModalToggle = () => {
    setIsModalOpen( ( isModalOpen ) => !isModalOpen );
    setIsRemovingUser( false );
  };

  const removeUsers = ( user: string ) => {
    setRhatUUIDtoDel( user );
    setIsModalOpen(true);
  }

  /**
   *
   * Remove Admins/Users from the database permissions
   */
  const doRemoveUsers = () => {
    setIsRemovingUser(true);
    const permissions = admin ? db.permissions.admins : db.permissions.users;
    const index = permissions.findIndex( ( item: any ) => item.indexOf( rhatUUIDtoDel ) > 0 );
    permissions.splice( index, 1 );
    gqlClient({
        query: manageAppDatabase,
        variables: {
          id: appId,
          databaseName: db.name,
          permissions: db.permissions,
        },
      })
        .then( ( res: any ) => {
          if ( res?.errors ) {
            throw res.errors;
          }
          window.OpNotification?.success({
            subject: 'Member permission deleted successfully!',
          });
          forceRefreshApp( res.data.manageAppDatabase );
          setIsModalOpen( false );
          setIsRemovingUser( false );
        })
        .catch((err: any) => {
          window.OpNotification?.danger({
            subject: 'An error occurred when deleting member permissions',
            body: err[0].message,
          } );
          setIsRemovingUser( false );
        });
  }
  /**
   * @param admin whether to use the admins
   * @param db database details
   * @returns UserQueries
   */
  const getUserQueries = (admin: boolean, db: App.Database ) => {
    const users = admin
      ? db.permissions.admins
      : db.permissions.users;
    return users
      .filter(admin => admin.startsWith('user:'))
      .map((admin, index) => {
        const rhatUUID = admin.slice(5, admin.length);
        return `
        admin${index}:getUsersBy(rhatUUID:"${rhatUUID}") {
          cn
          rhatUUID
        }
      `;
      });
  };
  /**
   * @param res response from userData fetch request
   * @param admin props of the component
   * @param db database details
   * @returns memberRows
   */
  const getMemberRows = ( res: any, admin: boolean, db: App.Database ) => {
    const users = admin
      ? db.permissions.admins
      : db.permissions.users;
    const userMap = Object.values(res.data).map((userData: any) => userData[0]);
    return users.map(admin => {
      const matchedUsers = userMap.find(user => {
        return user.rhatUUID === admin.slice(5, admin.length);
      });
      if (matchedUsers) {
        return [matchedUsers.cn, '', matchedUsers.rhatUUID];
      } else {
        return [admin, '', admin];
      }
    });
  };

  const getLabelOrAction = (
    cell: string,
    cellIndex: number,
    admin: boolean
  ) => {
    if (cellIndex === 1) {
      let labels = (
        <Label color="blue" isTruncated name="permission">
          Read
        </Label>
      );
      if (admin) {
        labels = (
          <LabelGroup name="permission">
            <Label color="blue">Read</Label>
            <Label color="blue">Write</Label>
          </LabelGroup>
        );
      }
      return labels;
    } else if (cellIndex === 2) {
      return (
        <Button variant="link"
          onClick={ () => { removeUsers( cell ); } }
          icon={ <TimesIcon /> }
        ></Button>
      );
    } else {
      return cell;
    }
  };

  return (
    <>
      {isUserDataLoading ? (
        <Loader />
      ) : (
        <TableComposable aria-label="Simple table" variant={'compact'}>
          <Thead>
            <Tr>
              {columns.map((column, columnIndex) => (
                <Th key={columnIndex}>{column}</Th>
              ))}
            </Tr>
          </Thead>
          <Tbody>
            {rows.map((row: string[], rowIndex: Key | null | undefined) => (
              <Tr key={rowIndex}>
                {row.map((cell: string, cellIndex: number) => (
                  <Td
                    key={`${rowIndex}_${cellIndex}`}
                    dataLabel={columns[cellIndex]}
                  >
                    { getLabelOrAction(cell, cellIndex, admin) }
                  </Td>
                ))}
              </Tr>
            ))}
          </Tbody>
        </TableComposable>
      ) }
      <Modal
        variant={ModalVariant.small}
        title={`Are you sure to remove the user?`}
        titleIconVariant="danger"
        isOpen={isModalOpen}
        onClose={handleModalToggle}
        actions={[
          <Button
            key="confirm"
            variant="danger"
            isLoading={ isRemovingUser }
            onClick={doRemoveUsers}
          >
            Yes, Remove
          </Button>,
          <Button key="cancel" variant="link" onClick={handleModalToggle}>
            Cancel
          </Button>,
        ]}
      >
        You are removing the user from the Database, this operation will restrict the user from accessing the selected database.
      </Modal>
    </>
  );
}