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 |
// 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 |
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 |
/**
* 检查元素是否存在
* - 如果元素不存在,移除当前动画
* - 如果元素存在,但没有挂载在文档中,移除当前动画与元素的信息
*/
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 |
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 |
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 |
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 |
// 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 |
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 |
/**
* 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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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>
</>
);
}