react-use#useDebounce TypeScript Examples
The following examples show how to use
react-use#useDebounce.
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: index.tsx From erda-ui with GNU Affero General Public License v3.0 | 6 votes |
DebounceSearch = (props: IProps) => {
const { onChange, value: pValue, className = '', placeholder = i18n.t('Search by keyword-char'), ...rest } = props;
const [value, setValue] = React.useState(undefined as string | undefined);
React.useEffect(() => {
setValue(pValue);
}, [pValue]);
useDebounce(
() => {
onChange && onChange(value);
},
600,
[value],
);
return (
<Search
className={`search-input ${className}`}
value={value}
placeholder={placeholder}
onChange={(e: any) => {
setValue(e.target.value);
}}
{...rest}
/>
);
}
Example #2
Source File: index.ts From erda-ui with GNU Affero General Public License v3.0 | 6 votes |
useWatchTableWidth = (selector: string) => {
const client = layoutStore.useStore((s) => s.client);
const [tableWidth, setTableWidth] = React.useState(0);
React.useLayoutEffect(() => {
setTableWidth(get(document.querySelector(selector), ['clientWidth'], 0));
}, [selector]);
useDebounce(
() => {
setTableWidth(get(document.querySelector(selector), ['clientWidth'], 0));
},
50,
[client],
);
return [tableWidth];
}
Example #3
Source File: useDebounceEffect.ts From ace with GNU Affero General Public License v3.0 | 6 votes |
useChangeDebounce = (fn: Function, ms?: number, deps?: DependencyList) => {
const lastDepValues = useRef(deps);
useDebounce(() => {
if (lastDepValues.current.some((it, index) => it !== deps[index])) {
fn();
}
lastDepValues.current = deps;
}, 500, [deps, fn]);
}
Example #4
Source File: HoverLinkPreview.tsx From logseq-plugin-link-preview with MIT License | 6 votes |
function useDebounceValue<T>(v: T, timeout: number = 50) {
const [state, setState] = React.useState(v);
useDebounce(
() => {
setState(v);
},
timeout,
[v]
);
return state;
}
Example #5
Source File: SearchInput.tsx From phosphor-home with MIT License | 5 votes |
SearchInput: React.FC<SearchInputProps> = () => {
const [value, setValue] = useState<string>("");
const [query, setQuery] = useRecoilState(searchQueryAtom);
const inputRef = useRef<HTMLInputElement>() as MutableRefObject<HTMLInputElement>;
useHotkeys("ctrl+k,cmd+k", (e) => {
e.preventDefault();
if (!e.repeat) {
inputRef.current?.focus();
inputRef.current.select();
}
});
/* eslint-disable react-hooks/exhaustive-deps */
useEffect(() => {
let isMounted = true;
if (value !== query) {
isMounted && setValue(query);
ReactGA.event({ category: "Search", action: "Tag", label: query });
}
return () => void (isMounted = false);
}, [query]);
/* eslint-enable react-hooks/exhaustive-deps */
const [isReady] = useDebounce(
() => {
if (value !== query) {
setQuery(value);
!!value &&
ReactGA.event({ category: "Search", action: "Query", label: value });
}
!!value &&
void document
.getElementById("beacon")
?.scrollIntoView({ block: "start", behavior: "smooth" });
},
500,
[value]
);
const handleCancelSearch = () => {
setValue("");
// Should cancel pending debounce timeouts and immediately clear query
// without causing lag!
// setQuery("");
};
return (
<div className="search-bar">
<MagnifyingGlass id="search-icon" size={24} />
<input
ref={inputRef}
id="search-input"
title="Search for icon names, categories, or keywords"
aria-label="Search for an icon"
type="text"
autoCapitalize="off"
autoComplete="off"
value={value}
placeholder="Search"
onChange={({ currentTarget }) => setValue(currentTarget.value)}
onKeyPress={({ currentTarget, key }) =>
key === "Enter" && currentTarget.blur()
}
/>
{!value && !isMobile && <Keys>{isApple ? <Command /> : "Ctrl + "}K</Keys>}
{value ? (
isReady() ? (
<X className="clear-icon" size={18} onClick={handleCancelSearch} />
) : (
<HourglassHigh className="wait-icon" weight="fill" size={18} />
)
) : null}
</div>
);
}
Example #6
Source File: index.tsx From rocketredis with MIT License | 4 votes |
KeyList: React.FC = () => {
const parentRef = useRef(null)
const { width } = useWindowSize({ watch: false })
const { t } = useTranslation('keyList')
const [searchInputValue, setSearchInputValue] = useState('')
const [filter, setFilter] = useState('')
const [keys, setKeys] = useState<string[]>([])
const [currentConnection] = useRecoilState(currentConnectionState)
const [currentDatabase] = useRecoilState(currentDatabaseState)
const [currentKey, setCurrentKey] = useRecoilState(currentKeyState)
useDebounce(
() => {
setFilter(searchInputValue)
},
500,
[searchInputValue]
)
const filteredKeys = useMemo(() => {
if (!filter) {
return keys
}
return keys.filter(key => key.includes(filter))
}, [filter, keys])
const rowVirtualizer = useVirtual({
size: filteredKeys.length,
parentRef,
estimateSize: useCallback(() => 33, [])
})
const handleSearchInputChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
setSearchInputValue(e.target.value)
},
[]
)
const handleSelectKey = useCallback(
(key: string) => {
setCurrentKey(key)
},
[setCurrentKey]
)
useEffect(() => {
if (currentDatabase) {
loadKeysFromDatabase().then(loadedKeys => {
setKeys(loadedKeys)
})
}
}, [currentDatabase])
return (
<Container
width={(width - 300) / 2}
height={Infinity}
minConstraints={[500, Infinity]}
maxConstraints={[width - 300 - 100, Infinity]}
>
{currentDatabase ? (
<>
<Header>
<HeaderTextContainer>
<HeaderTitle>{currentConnection?.name}</HeaderTitle>
<HeaderDatabaseDetails>
<span>{currentDatabase?.name}</span>
<span>
{currentDatabase?.keys} {t('keys')}
</span>
</HeaderDatabaseDetails>
</HeaderTextContainer>
<SearchInput
onChange={handleSearchInputChange}
value={searchInputValue}
/>
</Header>
<KeyListWrapper ref={parentRef}>
<KeyListContainer
style={{
height: `${rowVirtualizer.totalSize}px`
}}
>
{rowVirtualizer.virtualItems.map(virtualRow => {
const key = filteredKeys[virtualRow.index]
return (
<Key
key={virtualRow.index}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualRow.size}px`,
transform: `translateY(${virtualRow.start}px)`
}}
>
<KeyTextContainer
selected={currentKey === key}
onClick={() => handleSelectKey(key)}
>
<KeyTitle>{key}</KeyTitle>
</KeyTextContainer>
</Key>
)
})}
</KeyListContainer>
</KeyListWrapper>
</>
) : (
<EmptyContent message={t('empty')} />
)}
</Container>
)
}
Example #7
Source File: index.tsx From erda-ui with GNU Affero General Public License v3.0 | 4 votes |
PureLoadMoreSelector = (props: IProps) => {
const {
className = '',
dropdownClassName = '',
dropdownStyle = '',
type: initType = SelectType.Normal,
mode,
value,
onChange = emptyFun,
onClickItem = emptyFun,
placeholder = i18n.t('Please Select'),
list = emptyArray,
showSearch = true,
allowClear = false,
disabled = false,
valueItemRender = defaultValueItemRender,
chosenItemConvert,
changeQuery = emptyFun,
quickSelect = null,
onDropdownVisible,
onVisibleChange,
dropdownMatchSelectWidth = true,
valueChangeTrigger = 'onChange',
forwardedRef,
resultsRender,
size = '',
bordered = true,
q: propsQ,
} = props;
const isMultiple = mode === 'multiple';
const [visible, setVisible] = React.useState(false);
const [chosenItem, setChosenItem] = React.useState([] as IOption[]);
const [q, setQ] = React.useState(undefined as string | undefined);
const [type, setType] = React.useState(initType);
const [displayValue, setDisplayValue] = React.useState([] as IOption[]);
const [contentWidth, setContentWidth] = React.useState('');
const [innerValue, setInnerValue] = React.useState([value] as any[]);
const [valueChanged, setValueChanged] = React.useState(false);
const [compId, setCompId] = React.useState(uuid());
const reqRef = React.useRef(null as any);
const searchRef = React.useRef(null);
const menuRef = React.useRef(null);
const valueRef = React.useRef(null);
useEffectOnce(() => {
document.body.addEventListener('click', dropdownHide);
if (forwardedRef) {
forwardedRef.current = {
show: (vis: boolean) => setVisible(vis),
};
}
return () => {
document.body.removeEventListener('click', dropdownHide);
};
});
React.useEffect(() => {
if (propsQ === undefined && q !== undefined) setQ(propsQ);
}, [propsQ]);
const searchRefCur = searchRef && searchRef.current;
React.useEffect(() => {
onDropdownVisible && onDropdownVisible(visible);
if (visible && searchRefCur) {
searchRefCur.focus();
}
}, [visible, searchRefCur]);
// 带上select的dropdownMatchSelectWidth特性
const dropdownMinWidth = get(document.querySelector(`.load-more-selector-dropdown-${compId}`), 'style.minWidth');
React.useEffect(() => {
if (dropdownMatchSelectWidth && dropdownMinWidth) {
setContentWidth(dropdownMinWidth);
}
}, [dropdownMinWidth, dropdownMatchSelectWidth]);
useDebounce(
() => {
// 如果是category时,清除q时不需要changeQuery,因为在changeCategory里会自动清理,此处阻止避免触发两个修改引起请求两次
if (!(initType === SelectType.Category && !q)) changeQuery(q);
},
600,
[q],
);
React.useEffect(() => {
if (initType === SelectType.Category) {
// 模式为category,内部根据q是否有值来切换模式
if (type === SelectType.Category && q) {
setType(SelectType.Normal);
}
if (type === SelectType.Normal && !q) {
setType(SelectType.Category);
}
}
}, [q, initType, type]);
React.useEffect(() => {
setInnerValue([value]);
}, [value]);
React.useEffect(() => {
!visible && valueChangeTrigger === 'onClose' && valueChanged && onChange(...innerValue);
setValueChanged(false);
}, [visible]);
React.useEffect(() => {
if (isNumber(innerValue[0])) return setChosenItem(changeValue(innerValue[0], list, chosenItem));
!isEmpty(innerValue[0]) ? setChosenItem(changeValue(innerValue[0], list, chosenItem)) : setChosenItem([]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [innerValue, list]);
React.useEffect(() => {
if (chosenItemConvert) {
const val = map(chosenItem, (item) => {
if (item.label) return item;
const curVal = find(displayValue, (cItem) => `${cItem.value}` === `${item.value}`) || {};
return !isEmpty(curVal) ? { ...item, ...curVal } : item;
});
const getNewValue = () => {
reqRef.current = chosenItemConvert(isMultiple ? val : val[0], list);
if (isPromise(reqRef.current)) {
reqRef.current.then((res: IOption[]) => {
setDisplayValue(Array.isArray(res) ? res : [res]);
});
} else {
reqRef.current
? setDisplayValue(Array.isArray(reqRef.current) ? reqRef.current : [reqRef.current])
: setDisplayValue([]);
}
};
if (isPromise(reqRef.current)) {
reqRef.current.then(() => {
getNewValue();
});
} else {
getNewValue();
}
} else {
setDisplayValue(chosenItem);
}
}, [chosenItem]);
const dropdownHide = (e: any) => {
// 点击外部,隐藏选项
const menuEl = menuRef && menuRef.current;
const valueEl = valueRef && valueRef.current;
// eslint-disable-next-line react/no-find-dom-node
const el1 = ReactDOM.findDOMNode(menuEl) as HTMLElement;
// eslint-disable-next-line react/no-find-dom-node
const el2 = ReactDOM.findDOMNode(valueEl) as HTMLElement;
if (!((el1 && el1.contains(e.target)) || (el2 && el2.contains(e.target)))) {
setVisible(false);
}
};
const setValue = (v: IOption[]) => {
const newValue = isMultiple ? map(v, 'value') : get(v, '[0].value');
if (isEqual(newValue, innerValue[0])) return;
const [vals, opts] = isMultiple ? [map(v, 'value'), v] : [get(v, '[0].value'), v[0]];
setInnerValue([vals, opts]);
setValueChanged(true);
(!visible || valueChangeTrigger === 'onChange') && onChange(vals, opts);
};
const clearValue = () => {
setValue([]);
};
const deleteValue = (item: IOption) => {
// 如果单选且不允许清除,则不能删数据
if (!isMultiple && !allowClear) return;
setValue(filter(chosenItem, (c) => `${c.value}` !== `${item.value}`));
};
const addValue = (item: IOption) => {
if (isMultiple) {
if (find(chosenItem, (cItem) => `${cItem.value}` === `${item.value}`)) {
setValue(
map(chosenItem, (cItem) => {
return cItem.value === item.value ? { ...item } : { ...cItem };
}),
);
} else {
setValue([...chosenItem, item]);
}
} else {
setValue([item]);
}
};
const clickItem = (item: IOption, check: boolean) => {
check ? addValue(item) : deleteValue(item);
if (!isMultiple && check) setVisible(false); // 非多选模式,选中后隐藏
onClickItem(item);
};
const getOverlay = () => {
const Comp = CompMap[type];
const curQuickSelect = isArray(quickSelect) ? quickSelect : quickSelect ? [quickSelect] : [];
return (
<Menu className="load-more-dropdown-menu" ref={menuRef} style={{ width: contentWidth }}>
{showSearch
? [
<MenuItem key="_search-item">
<div>
<Input
ref={searchRef}
size="small"
className="search"
prefix={<ErdaIcon type="search" size={'16'} fill="default-3" />}
placeholder={i18n.t('Search by keyword-char')}
value={q}
onChange={(e) => setQ(e.target.value)}
/>
</div>
</MenuItem>,
]
: null}
{isMultiple
? [
<MenuItem className="chosen-info" key="_chosen-info-item">
<div className="w-full flex my-1">
<div>
{i18n.t('common:Selected')}
<span className="mx-0.5">{chosenItem.length}</span>
{i18n.t('common:item')}
</div>
{chosenItem.length ? (
<span className="fake-link ml-4 text-purple-deep" onClick={clearValue}>
{i18n.t('common:Clear selected')}
</span>
) : null}
</div>
</MenuItem>,
<Menu.Divider key="_chosen-info-divider" />,
]
: null}
{curQuickSelect.map((quickSelectItem, idx) => {
return [<MenuItem key={`quick-select-${idx}`}>{quickSelectItem}</MenuItem>, <Menu.Divider />];
})}
<MenuItem className="options" key="options">
<Comp {...props} width={contentWidth} clickItem={clickItem} value={chosenItem} isMultiple={isMultiple} />
{/* {
isMultiple && (
<div className={`chosen-info ${type === SelectType.Normal ? 'border-top' : ''}`}>
{i18n.t('common:Selected')}
<span>{chosenItem.length}</span>
{i18n.t('common:item')}
</div>
)
} */}
</MenuItem>
</Menu>
);
};
return (
<div className={`load-more-selector ${className}`} ref={valueRef} onClick={(e) => e.stopPropagation()}>
<Dropdown
overlay={getOverlay()}
visible={visible}
overlayClassName={`load-more-selector-dropdown load-more-selector-dropdown-${compId} ${dropdownClassName}`}
overlayStyle={dropdownStyle}
onVisibleChange={(visible) => onVisibleChange?.(visible, innerValue)}
>
<div
className={`results cursor-pointer ${disabled ? 'not-allowed' : ''} ${size} ${bordered ? '' : 'border-none'}`}
onClick={() => {
!disabled && !visible && setVisible(true);
}}
>
{resultsRender ? (
resultsRender(displayValue, deleteValue, isMultiple, list)
) : (
<div className="values">
{map(displayValue, (item) => (
<div key={item.value} className="value-item">
{valueItemRender(item, deleteValue, isMultiple, list)}
</div>
))}
{placeholder && isEmpty(chosenItem) ? <span className="placeholder">{placeholder}</span> : null}
</div>
)}
{allowClear && !isEmpty(chosenItem) ? (
<ErdaIcon
type="close-one"
className="close"
size="14px"
onClick={(e: any) => {
e.stopPropagation();
clearValue();
}}
/>
) : null}
</div>
</Dropdown>
</div>
);
}
Example #8
Source File: index.tsx From erda-ui with GNU Affero General Public License v3.0 | 4 votes |
ApiMarketList = () => {
const [{ keyword, ...state }, updater, update] = useUpdate<IState>({
keyword: '',
visible: false,
scope: 'asset',
mode: 'add',
assetDetail: {},
showApplyModal: false,
showExportModal: false,
});
const [assetList, assetListPaging] = apiMarketStore.useStore((s) => [s.assetList, s.assetListPaging]);
const { scope } = routeInfoStore.useStore((s) => s.params) as { scope: API_MARKET.AssetScope };
const { getAssetList } = apiMarketStore.effects;
const { resetAssetList } = apiMarketStore.reducers;
const [isFetchList] = useLoading(apiMarketStore, ['getAssetList']);
useUnmount(() => {
resetAssetList();
});
const getList = (params: Pick<API_MARKET.QueryAssets, 'keyword' | 'pageNo' | 'scope' | 'pageSize'>) => {
getAssetList({
...commonQuery,
...params,
});
};
useDebounce(
() => {
getList({ keyword, pageNo: 1, scope });
},
200,
[keyword, scope],
);
const reload = () => {
getList({ keyword, pageNo: 1, scope });
};
const filterConfig = React.useMemo(
(): Field[] => [
{
label: '',
type: 'input',
outside: true,
key: 'keyword',
placeholder: i18n.t('default:Search by keyword'),
customProps: {
autoComplete: 'off',
},
},
],
[],
);
const handleSearch = (query: Record<string, any>) => {
updater.keyword(query.keyword);
};
const handleTableChange = ({ pageSize, current }: PaginationProps) => {
getList({ keyword, pageNo: current, pageSize, scope });
};
const handleManage = ({ assetID }: API_MARKET.Asset) => {
goTo(goTo.pages.apiManageAssetVersions, { scope, assetID });
};
const gotoVersion = ({ asset, latestVersion }: API_MARKET.AssetListItem) => {
goTo(goTo.pages.apiManageAssetDetail, { assetID: asset.assetID, scope, versionID: latestVersion.id });
};
const handleApply = (record: API_MARKET.Asset) => {
update({
showApplyModal: true,
assetDetail: record || {},
});
};
const closeModal = () => {
update({
visible: false,
showApplyModal: false,
assetDetail: {},
});
};
const showAssetModal = (assetScope: IScope, mode: IMode, record?: API_MARKET.Asset) => {
update({
scope: assetScope,
mode,
visible: true,
assetDetail: record || {},
});
};
const toggleExportModal = () => {
updater.showExportModal((prev: boolean) => !prev);
};
const columns: Array<ColumnProps<API_MARKET.AssetListItem>> = [
{
title: i18n.t('API name'),
dataIndex: ['asset', 'assetName'],
width: 240,
},
{
title: i18n.t('API description'),
dataIndex: ['asset', 'desc'],
},
{
title: 'API ID',
dataIndex: ['asset', 'assetID'],
width: 200,
},
{
title: i18n.t('Update time'),
dataIndex: ['asset', 'updatedAt'],
width: 200,
render: (date) => moment(date).format('YYYY-MM-DD HH:mm:ss'),
},
{
title: i18n.t('Creator'),
dataIndex: ['asset', 'creatorID'],
width: 160,
render: (text) => (
<Tooltip title={<UserInfo id={text} />}>
<UserInfo.RenderWithAvatar id={text} />
</Tooltip>
),
},
];
const actions: IActions<API_MARKET.AssetListItem> = {
render: (record) => {
const { permission, asset } = record;
const { manage, addVersion, hasAccess } = permission;
return [
{
title: i18n.t('Export'),
onClick: () => {
exportApi
.fetch({
versionID: record.latestVersion.id,
specProtocol,
})
.then(() => {
toggleExportModal();
});
},
},
{
title: i18n.t('manage'),
onClick: () => {
handleManage(asset);
},
show: manage,
},
{
title: i18n.t('add {name}', { name: i18n.t('Version') }),
onClick: () => {
showAssetModal('version', 'add', asset);
},
show: !!addVersion,
},
{
title: i18n.t('apply to call'),
onClick: () => {
handleApply(asset);
},
show: hasAccess,
},
];
},
};
return (
<div className="api-market-list">
<TopButtonGroup>
<Button onClick={toggleExportModal}>{i18n.t('Export Records')}</Button>
<Button
type="primary"
onClick={() => {
showAssetModal('asset', 'add');
}}
>
{i18n.t('default:Add Resource')}
</Button>
</TopButtonGroup>
<ErdaTable
rowKey="asset.assetID"
columns={columns}
dataSource={assetList}
pagination={{
...assetListPaging,
current: assetListPaging.pageNo,
}}
onRow={(record) => {
return {
onClick: () => {
gotoVersion(record);
},
};
}}
onChange={handleTableChange}
loading={isFetchList}
actions={actions}
slot={<ConfigurableFilter fieldsList={filterConfig} onFilter={handleSearch} />}
/>
<AssetModal
visible={state.visible}
scope={state.scope}
mode={state.mode}
formData={state.assetDetail as API_MARKET.Asset}
onCancel={closeModal}
afterSubmit={reload}
/>
<ApplyModal
visible={state.showApplyModal}
onCancel={closeModal}
dataSource={state.assetDetail as API_MARKET.Asset}
/>
<ExportRecord visible={state.showExportModal} onCancel={toggleExportModal} />
</div>
);
}
Example #9
Source File: index.tsx From erda-ui with GNU Affero General Public License v3.0 | 4 votes |
MiddlewareDashboard = () => { const [projectList, middlewares, middelwaresPaging] = middlewareDashboardStore.useStore((s) => [ s.projectList, s.middlewares, s.middelwaresPaging, ]); const { getProjects, getMiddlewares, getAddonUsage, getAddonDailyUsage } = middlewareDashboardStore.effects; const { clearMiddlewares } = middlewareDashboardStore.reducers; const [getProjectsLoading, getMiddlewaresLoading] = useLoading(middlewareDashboardStore, [ 'getProjects', 'getMiddlewares', ]); const [workspace, setWorkspace] = useState('ALL'); const [projectId, setProjectId] = useState(); const [addonName, setAddonName] = useState(undefined as string | undefined); const [ip, setIp] = useState(undefined as string | undefined); useDebounce( () => { const searchQuery = { workspace: workspace === 'ALL' ? undefined : workspace, projectId, addonName, ip }; getMiddlewares(searchQuery); getAddonUsage(searchQuery); getAddonDailyUsage(searchQuery); return clearMiddlewares; }, 600, [workspace, projectId, addonName, ip, getMiddlewares, clearMiddlewares, getAddonUsage, getAddonDailyUsage], ); const handleEnvChange = (value: string) => { setWorkspace(value); }; const handleProjectChange = (value: string) => { setProjectId(value); }; const handleSearchProjects = (searchKey?: string) => { getProjects(searchKey); }; const handleSearchAddon = (searchKey?: string) => { setAddonName(searchKey || undefined); }; const handleSearchIp = (searchKey?: string) => { setIp(searchKey || undefined); }; useMount(() => { handleSearchProjects(); getAddonUsage(); getAddonDailyUsage(); }); // const overviewItemNameMap = { // cpu: 'CPU', // mem: i18n.t('memory'), // nodes: i18n.t('Node'), // }; const handleTableChange = (pagination: any) => { const { current, pageSize: page_Size } = pagination; getMiddlewares({ workspace: workspace === 'ALL' ? undefined : workspace, projectId, addonName, pageNo: current, pageSize: page_Size, }); }; const middlewareCols: Array<ColumnProps<MIDDLEWARE_DASHBOARD.IMiddlewareDetail>> = [ { title: i18n.t('addon'), dataIndex: 'name', key: 'name', width: '35%', render: (value: string) => <span className="hover-text font-bold">{value}</span>, }, { title: i18n.t('cluster'), dataIndex: 'clusterName', key: 'clusterName', width: '20%', }, { title: i18n.t('project'), dataIndex: 'projectName', key: 'projectName', width: '20%', }, { title: 'CPU', dataIndex: 'cpu', key: 'cpu', width: '10%', sorter: (a: MIDDLEWARE_DASHBOARD.IMiddlewareDetail, b: MIDDLEWARE_DASHBOARD.IMiddlewareDetail) => a.cpu - b.cpu, }, { title: i18n.t('memory'), dataIndex: 'mem', key: 'mem', width: '10%', render: (value: number) => getFormatter('CAPACITY', 'MB').format(value), sorter: (a: MIDDLEWARE_DASHBOARD.IMiddlewareDetail, b: MIDDLEWARE_DASHBOARD.IMiddlewareDetail) => a.mem - b.mem, }, { title: i18n.t('Node'), dataIndex: 'nodes', key: 'nodes', width: 80, sorter: (a: MIDDLEWARE_DASHBOARD.IMiddlewareDetail, b: MIDDLEWARE_DASHBOARD.IMiddlewareDetail) => a.nodes - b.nodes, }, { title: i18n.t('cmp:Number of references'), dataIndex: 'attachCount', key: 'attachCount', width: 200, sorter: (a: MIDDLEWARE_DASHBOARD.IMiddlewareDetail, b: MIDDLEWARE_DASHBOARD.IMiddlewareDetail) => a.attachCount - b.attachCount, }, ]; const { pageNo, pageSize, total } = middelwaresPaging; return ( <> <div className="middleware-dashboard-content"> <div className="middleware-dashboard-top mb-4"> <div className="filter-group-ct mb-4"> <Row gutter={20}> <Col span={6} className="filter-item"> <div className="filter-item-label">{firstCharToUpper(i18n.t('environment'))}</div> <Select className="filter-item-content" value={workspace} onChange={handleEnvChange}> {envOptions} </Select> </Col> <Col span={6} className="filter-item"> <div className="filter-item-label">{firstCharToUpper(i18n.t('project'))}</div> <Select showSearch className="filter-item-content" allowClear value={projectId} placeholder={i18n.t('Search by keyword')} notFoundContent={ <IF check={getProjectsLoading}> <Spin size="small" /> </IF> } filterOption={false} onSearch={handleSearchProjects} onChange={handleProjectChange} > {map(projectList, (d) => ( <Option key={d.id}>{d.name}</Option> ))} </Select> </Col> <Col span={6} className="filter-item"> <div className="filter-item-label">Addon</div> <Search allowClear className="filter-item-content" placeholder={i18n.t('search by Addon name or ID')} onChange={(e) => handleSearchAddon(e.target.value)} /> </Col> <Col span={6} className="filter-item"> <div className="filter-item-label">IP</div> <Search allowClear className="filter-item-content" placeholder={i18n.t('cmp:search by container IP')} onChange={(e) => handleSearchIp(e.target.value)} /> </Col> </Row> </div> {/* <Row className="middleware-overview-ct mb-4" type="flex" justify="space-between" gutter={50}> { map(overview, (v, k) => ( <Col span={8} key={k}> <div className="middleware-overview-item border-all"> <div className="title mb-5">{overviewItemNameMap[k]}</div> <div className="num">{v}</div> </div> </Col> )) } </Row> */} <div className="bg-white px-2 py-3"> <AddonUsageChart /> </div> </div> </div> <ErdaTable className="cursor-pointer" rowKey="instanceId" columns={middlewareCols} dataSource={middlewares} loading={getMiddlewaresLoading} pagination={{ current: pageNo, pageSize, total, }} onRow={({ instanceId }: MIDDLEWARE_DASHBOARD.IMiddlewareDetail) => ({ onClick: () => { goTo(`./${instanceId}/monitor`); }, })} onChange={handleTableChange} scroll={{ x: '100%' }} /> </> ); }
Example #10
Source File: index.tsx From erda-ui with GNU Affero General Public License v3.0 | 4 votes |
ServiceManager = () => {
const [propsContainerList, propsServiceList, runtimeJson, runtimeStatus, serviceReqStatus, metrics] =
dcosServiceStore.useStore((s) => [
s.containerList,
s.serviceList,
s.runtimeJson,
s.runtimeStatus,
s.serviceReqStatus,
s.metrics,
]);
const list = clusterStore.useStore((s) => s.list);
const { getClusterList } = clusterStore.effects;
const { getContainerList, getServiceList, getRuntimeJson, getRuntimeStatus } = dcosServiceStore.effects;
const { clearRuntimeJson, clearRuntimeStatus } = dcosServiceStore.reducers;
const [isFetchingClusters] = useLoading(clusterStore, ['getClusterList']);
const [isFetchingServices, isFetchingContainers] = useLoading(dcosServiceStore, [
'getServiceList',
'getContainerList',
]);
const [{ path, cluster, environment, ip, serviceList, containerList }, updater, update] = useUpdate<IState>({
path: [{ q: '', name: '' }],
cluster: '',
environment: 'dev',
ip: undefined,
serviceList: [],
containerList: [],
});
React.useEffect(() => {
update({
serviceList: propsServiceList,
containerList: propsContainerList,
});
}, [update, propsContainerList, propsServiceList]);
useEffectOnce(() => {
getClusterList().then((_list: ORG_CLUSTER.ICluster[]) => {
!isEmpty(_list) &&
update({
cluster: _list[0].name,
path: [{ q: _list[0].name, name: _list[0].name }],
});
});
return () => {
clearInterval(reqSt);
};
});
const fetchServiceList = React.useCallback(
(q: { paths: DCOS_SERVICES.path[]; environment: string; ip?: string }) => {
const depth = q.paths.length;
if (depth < 5 && depth > 0) {
getServiceList(q);
}
},
[getServiceList],
);
useDebounce(
() => {
fetchServiceList({ paths: path, environment, ip });
},
300,
[ip, path, environment],
);
useUpdateEffect(() => {
clearInterval(reqSt);
if (['runtime', 'service'].includes(curLevel() as string)) {
reqRuntimeStatus();
reqSt = setInterval(() => reqRuntimeStatus(), 5000);
}
}, [serviceList]);
useUpdateEffect(() => {
combineStatuToService(runtimeStatus);
}, [runtimeStatus]);
useUpdateEffect(() => {
const { cpu, mem } = metrics;
cpu?.loading === false && mem?.loading === false && combineMetricsToList(formatMetricsToObj(metrics)); // 两部分数据都返回后才开始combine数据
}, [metrics]);
useUpdateEffect(() => {
if (!serviceReqStatus) {
clearInterval(reqSt);
}
}, [serviceReqStatus]);
const formatMetricsToObj = (_metrics: {
cpu: { data: Array<{ tag: string; data: number }> };
mem: { data: Array<{ tag: string; data: number }> };
}) => {
const metricsObj = {};
const { cpu, mem } = _metrics;
(cpu.data || []).forEach((cItem) => {
cItem.tag && (metricsObj[cItem.tag] = { cpuUsagePercent: cItem.data / 100 || 0, diskUsage: 0 });
});
(mem.data || []).forEach((mItem) => {
if (mItem.tag) {
!metricsObj[mItem.tag] && (metricsObj[mItem.tag] = {});
metricsObj[mItem.tag].memUsage = mItem.data || 0;
}
});
return metricsObj;
};
const combineMetricsToList = (metricsObj: Obj) => {
let newContainerList = [...containerList];
let newServiceList = [...serviceList];
if (curLevel('container')) {
// 当前层级在container上,
newContainerList = newContainerList.map((item) => {
let _metrics = null;
try {
_metrics = metricsObj[item.containerId] || null;
} catch (e) {
_metrics = null;
}
return { ...item, metrics: _metrics };
});
updater.containerList(newContainerList);
} else {
const combineKye = curLevel('service') ? 'name' : 'id';
newServiceList = newServiceList.map((item) => {
let _metrics = null;
try {
_metrics = metricsObj[item[combineKye]] || null;
} catch (e) {
_metrics = null;
}
return { ...item, metrics: _metrics };
});
updater.serviceList(newServiceList);
}
};
const onJsonShow = (_visible: boolean) => {
const runtimeId = getLevel('runtime').id;
_visible && runtimeJson === null && runtimeId !== 'unknown' && getRuntimeJson({ runtimeId });
};
const combineStatuToService = (_runtimeStatus: Obj) => {
let newServiceList = [...serviceList];
if (curLevel('runtime')) {
// runtime的status在runtimeStatus中runtimeId为key对象中
newServiceList = newServiceList.map((item) => {
let status = '';
try {
status = runtimeStatus[item.id].status || '';
} catch (e) {
status = '';
}
return { ...item, status };
});
} else if (curLevel('service')) {
// service的status在runtimeStatus对应runtimeId为key的对象中的more字段中
const runtimeId = getLevel('runtime').id;
newServiceList = newServiceList.map((item) => {
let status = '';
try {
status = runtimeStatus[runtimeId].more[item.name] || '';
} catch (e) {
status = '';
}
return { ...item, status };
});
}
updater.serviceList(newServiceList);
};
const into = (p: { q: string; name: string }) => {
if (curLevel('runtime')) clearRuntimeJson();
const newPath = path.concat(p);
update({
path: newPath,
});
const depth = newPath.length;
if (depth >= 5) {
getContainerList(newPath);
}
};
const backTo = (depth: number) => {
if (curLevel('runtime')) clearRuntimeJson();
update({
path: path.slice(0, depth + 1),
});
};
const curLevel = (lev = '') => {
const levArr = ['project', 'application', 'runtime', 'service', 'container'];
const curLev = levArr[path.length - 1];
return lev ? lev === curLev : curLev;
};
const getLevel = (lev = '') => {
const levs = {
project: path[1] ? { id: path[1].q, name: path[1].name } : null,
application: path[2] ? { id: path[2].q, name: path[2].name } : null,
runtime: path[3] ? { id: path[3].q, name: path[3].name } : null,
service: path[4] ? { id: path[4].q, name: path[4].name } : null,
};
return levs[lev] || null;
};
const handleEnvChange = (_environment: string) => {
update({ environment: _environment });
};
const handleClusterChange = (_cluster: string) => {
update({
cluster: _cluster,
path: [{ q: _cluster, name: cluster }],
});
};
const reqRuntimeStatus = () => {
let runtimeIds = '';
if (curLevel('runtime')) {
// runtime,批量查询runtime的状态
runtimeIds = map(serviceList, 'id').join(',');
} else if (curLevel('service')) {
// service,查询单个runtime状态
runtimeIds = getLevel('runtime').id;
}
if (runtimeIds && runtimeIds !== 'unknown') {
getRuntimeStatus({ runtimeIds });
} else {
clearInterval(reqSt);
}
};
const jsonString = runtimeJson === null ? '' : JSON.stringify(runtimeJson, null, 2);
const slot = (
<IF check={path.length === 1}>
<div className="filter-group mb-4 ml-3-group">
<Select
value={cluster}
className="w-[150px] bg-black-06 rounded"
bordered={false}
onChange={handleClusterChange}
>
{map(list, (v) => (
<Option key={v.name} value={v.name}>
{v.displayName || v.name}
</Option>
))}
</Select>
<Input
allowClear
value={ip}
className="w-[150px] bg-black-06 rounded"
placeholder={i18n.t('cmp:Search by IP')}
onChange={(e) => update({ ip: e.target.value })}
/>
<Select
value={environment}
className="w-[150px] bg-black-06 rounded"
bordered={false}
onChange={handleEnvChange}
>
{map(ENV_MAP, (v, k) => (
<Option key={k} value={k}>
{v.cnName}
</Option>
))}
</Select>
</div>
</IF>
);
return (
<Spin spinning={isFetchingClusters}>
<Holder when={isEmpty(list)}>
<Breadcrumb
separator={<ErdaIcon className="text-xs align-middle" type="right" size="14px" />}
className="path-breadcrumb"
>
{path.map((p, i) => {
const isLast = i === path.length - 1;
return (
<Breadcrumb.Item
key={i}
className={isLast ? '' : 'hover-active'}
onClick={() => {
if (!isLast) backTo(i);
}}
>
{p.name}
</Breadcrumb.Item>
);
})}
</Breadcrumb>
<div className="to-json">
{path.length === 4 ? (
<JsonChecker
buttonText={i18n.t('runtime configs')}
jsonString={jsonString}
onToggle={onJsonShow}
modalConfigs={{ title: i18n.t('runtime configs') }}
/>
) : null}
</div>
</Holder>
<Spin spinning={isFetchingServices || isFetchingContainers}>
<PureServiceList
into={into}
slot={slot}
depth={path.length}
serviceList={serviceList}
onReload={
path.length < 5 ? () => fetchServiceList({ paths: path, environment, ip }) : () => getContainerList(path)
}
containerList={containerList}
haveMetrics={false}
extraQuery={{ filter_cluster_name: cluster }}
/>
{path.length === 2 ? (
<AssociatedAddons projectId={path[1].q} environment={ENV_MAP[environment].enName} />
) : null}
</Spin>
</Spin>
);
}
Example #11
Source File: publisher-list-v2.tsx From erda-ui with GNU Affero General Public License v3.0 | 4 votes |
PurePublisherList = ({
list = [],
paging,
getList,
clearList,
isFetching,
onItemClick,
}: IPubliserListProps) => {
const [{ q, formVisible }, updater] = useUpdate({
q: undefined as string | undefined,
formVisible: false,
});
const { mode } = routeInfoStore.useStore((s) => s.params);
const publishOperationAuth = usePerm((s) => s.org.publisher.operation.pass);
useUnmount(clearList);
useUpdateEffect(() => {
updater.q(undefined);
}, [mode]);
useDebounce(
() => {
getList({
q,
pageNo: 1,
type: mode,
});
},
300,
[q, mode],
);
const onSubmit = ({ q: value }: { q: string }) => {
updater.q(value);
};
const goToPublisher = (item: PUBLISHER.IPublisher) => {
onItemClick(item);
};
const handlePageChange = (pageNo: number, pageSize: number) => {
getList({
q,
pageNo,
pageSize,
type: mode,
});
};
const openFormModal = () => {
updater.formVisible(true);
};
const closeFormModal = () => {
updater.formVisible(false);
};
const afterSubmitAction = (_isUpdate: boolean, data: PUBLISHER.IArtifacts) => {
const { id, type } = data;
goTo(`../${type}/${id}`);
};
const column: Array<ColumnProps<PUBLISHER.IPublisher>> = [
{
title: i18n.t('publisher:publisher content name'),
dataIndex: 'name',
width: 240,
},
{
title: i18n.t('Description'),
dataIndex: 'desc',
},
...insertWhen<ColumnProps<PUBLISHER.IPublisher>>(mode === 'LIBRARY', [
{
title: i18n.t('version number'),
width: 160,
dataIndex: 'latestVersion',
render: (text) => text || '-',
},
{
title: i18n.t('publisher:subscriptions'),
width: 120,
dataIndex: 'refCount',
render: (text) => text || 0,
},
]),
{
title: i18n.t('default:status'),
width: 120,
dataIndex: 'public',
render: (bool) => {
return (
<span className={`item-status ${bool ? 'on' : 'off'}`}>
{bool ? i18n.t('publisher:published') : i18n.t('publisher:withdrawn')}
</span>
);
},
},
];
const config = React.useMemo(
() => [
{
type: Input,
name: 'q',
customProps: {
placeholder: i18n.t('filter by {name}', { name: i18n.t('publisher:publisher content name') }),
autoComplete: 'off',
},
},
],
[],
);
return (
<Spin spinning={isFetching}>
<div className="publisher-list-section">
<TopButtonGroup>
<WithAuth pass={publishOperationAuth} tipProps={{ placement: 'bottom' }}>
<Button type="primary" onClick={() => openFormModal()}>
{i18n.t('publisher:Add')}
</Button>
</WithAuth>
</TopButtonGroup>
<ErdaTable
rowKey="id"
columns={column}
dataSource={list}
onRow={(record: PUBLISHER.IPublisher) => {
return {
onClick: () => {
goToPublisher(record);
},
};
}}
pagination={{
current: paging.pageNo,
...paging,
onChange: handlePageChange,
}}
slot={
<CustomFilter
key={mode}
config={config}
onSubmit={onSubmit}
onReset={() => {
updater.q('');
}}
/>
}
/>
<ArtifactsFormModal visible={formVisible} onCancel={closeFormModal} afterSubmit={afterSubmitAction} />
</div>
</Spin>
);
}
Example #12
Source File: useEstimatedOutput.ts From mStable-apps with GNU Lesser General Public License v3.0 | 4 votes |
useEstimatedOutput = (
inputValue?: BigDecimalInputValue,
outputValue?: BigDecimalInputValue,
lpPriceAdjustment?: LPPriceAdjustment,
shouldSkip?: boolean,
): Output => {
const inputValuePrev = usePrevious(inputValue)
const outputValuePrev = usePrevious(outputValue)
const [estimatedOutputRange, setEstimatedOutputRange] = useFetchState<[BigDecimal, BigDecimal]>()
const [action, setAction] = useState<Action | undefined>()
const signer = useSigner()
const massetState = useSelectedMassetState() as MassetState
const { address: massetAddress, fAssets, bAssets, feeRate: swapFeeRate, redemptionFeeRate } = massetState
const poolAddress = Object.values(fAssets).find(
f =>
f.feederPoolAddress === inputValue?.address ||
f.feederPoolAddress === outputValue?.address ||
f.address === inputValue?.address ||
f.address === outputValue?.address,
)?.feederPoolAddress
const inputRatios = useMassetInputRatios()
const scaledInput = useScaledInput(inputValue, outputValue, inputRatios)
const contract: Contract | undefined = useMemo(() => {
if (!signer) return
// use feeder pool to do swap
if (poolAddress) {
return FeederPool__factory.connect(poolAddress, signer)
}
return Masset__factory.connect(massetAddress, signer)
}, [poolAddress, massetAddress, signer])
const isFeederPool = contract?.address === poolAddress
const exchangeRate = useMemo<FetchState<number>>(() => {
if (shouldSkip) return {}
if (estimatedOutputRange.fetching) return { fetching: true }
if (!scaledInput?.high || !outputValue || !estimatedOutputRange.value) return {}
const [, high] = estimatedOutputRange.value
if (!high.exact.gt(0) || !scaledInput.high.exact.gt(0)) {
return { error: 'Amount must be greater than zero' }
}
const value = high.simple / scaledInput.high.simple
return { value }
}, [estimatedOutputRange, scaledInput, outputValue, shouldSkip])
const feeRate = useMemo<FetchState<BigDecimal>>(() => {
if (shouldSkip || !withFee.has(action) || !estimatedOutputRange.value?.[1]) return {}
if (estimatedOutputRange.fetching) return { fetching: true }
const _feeRate = action === Action.SWAP ? swapFeeRate : redemptionFeeRate
const swapFee = estimatedOutputRange.value[1]
.scale()
.divPrecisely(BigDecimal.ONE.sub(_feeRate))
.sub(estimatedOutputRange.value[1].scale())
return { value: swapFee }
}, [action, estimatedOutputRange, swapFeeRate, redemptionFeeRate, shouldSkip])
const priceImpact = useMemo<FetchState<PriceImpact>>(() => {
if (estimatedOutputRange.fetching || !estimatedOutputRange.value) return { fetching: true }
if (!scaledInput || !scaledInput.high.exact.gt(0)) return {}
const value = getPriceImpact([scaledInput.scaledLow, scaledInput.scaledHigh], estimatedOutputRange.value, lpPriceAdjustment)
return { value }
}, [estimatedOutputRange.fetching, estimatedOutputRange.value, lpPriceAdjustment, scaledInput])
/*
* |------------------------------------------------------|
* | ROUTES |
* | -----------------------------------------------------|
* | Input | Output | Function | Tokens |
* | -----------------------------------------------------|
* | basset | masset | masset mint | 1 basset, 1 masset |
* | masset | basset | masset redeem | 1 masset, 1 basset |
* | basset | basset | masset swap | 2 bassets |
* | fasset | basset | fpool swap | 1 fasset |
* | fasset | masset | fpool swap | 1 fasset |
* |------------------------------------------------------|
*/
const inputEq = inputValuesAreEqual(inputValue, inputValuePrev)
const outputEq = inputValuesAreEqual(outputValue, outputValuePrev)
const eq = inputEq && outputEq
const [update] = useDebounce(
() => {
if (!scaledInput || !outputValue || shouldSkip || !contract) return
const { address: inputAddress, decimals: inputDecimals } = inputValue
const { address: outputAddress, decimals: outputDecimals } = outputValue
const isLPRedeem = contract.address === inputAddress
const isLPMint = contract.address === outputAddress
const isMassetMint = bAssets[inputAddress]?.address && outputAddress === massetAddress
const isBassetSwap = [inputAddress, outputAddress].filter(address => bAssets[address]?.address).length === 2
const isInvalid = inputAddress === outputAddress
if (!scaledInput.high.exact.gt(0)) return
// same -> same; fallback to input value 1:1
if (isInvalid) {
setEstimatedOutputRange.value([scaledInput.scaledLow, scaledInput.high])
return
}
let outputLowPromise: Promise<BigNumber> | undefined
let outputHighPromise: Promise<BigNumber> | undefined
if (isMassetMint || isLPMint) {
setAction(Action.MINT)
outputLowPromise = contract.getMintOutput(inputAddress, scaledInput.low.scale(inputDecimals).exact)
outputHighPromise = contract.getMintOutput(inputAddress, scaledInput.high.exact)
} else if ((isFeederPool || isBassetSwap) && !isLPRedeem) {
setAction(Action.SWAP)
outputLowPromise = contract.getSwapOutput(inputAddress, outputAddress, scaledInput.low.scale(inputDecimals).exact)
outputHighPromise = contract.getSwapOutput(inputAddress, outputAddress, scaledInput.high.exact)
} else if (!isFeederPool || isLPRedeem) {
setAction(Action.REDEEM)
outputLowPromise = contract.getRedeemOutput(outputAddress, scaledInput.low.scale(inputDecimals).exact)
outputHighPromise = contract.getRedeemOutput(outputAddress, scaledInput.high.exact)
}
if (outputLowPromise && outputHighPromise) {
setEstimatedOutputRange.fetching()
Promise.all([outputLowPromise, outputHighPromise])
.then(data => {
const [_low, _high] = data
const low = new BigDecimal(_low, outputDecimals)
const high = new BigDecimal(_high, outputDecimals)
setEstimatedOutputRange.value([low, high])
})
.catch(_error => {
setEstimatedOutputRange.error(sanitizeMassetError(_error))
})
return
}
setEstimatedOutputRange.value()
},
2500,
[eq],
)
useEffect(() => {
if (shouldSkip) return
if (!eq && contract && scaledInput && outputValue) {
if (scaledInput.high.exact.gt(0)) {
setEstimatedOutputRange.fetching()
update()
} else {
setEstimatedOutputRange.value()
}
}
}, [eq, contract, setEstimatedOutputRange, update, scaledInput, outputValue, shouldSkip])
return useMemo(
() => ({
estimatedOutputAmount: {
fetching: estimatedOutputRange.fetching,
error: estimatedOutputRange.error,
value: estimatedOutputRange.value?.[1],
},
priceImpact,
exchangeRate,
feeRate,
}),
[estimatedOutputRange, priceImpact, exchangeRate, feeRate],
)
}
Example #13
Source File: useEstimatedOutputMulti.ts From mStable-apps with GNU Lesser General Public License v3.0 | 4 votes |
useEstimatedOutputMulti = (
route: Route,
scaledInputs: ScaledInputs,
lpPriceAdjustment?: LPPriceAdjustment,
contract?: MintableContract,
): Output => {
const [estimatedOutputRange, setEstimatedOutputRange] = useFetchState<[BigDecimal, BigDecimal]>()
const priceImpact = useMemo<FetchState<PriceImpact>>(() => {
if (estimatedOutputRange.fetching) return { fetching: true }
if (scaledInputs.scaledHighTotal.exact.eq(0) || !estimatedOutputRange.value) return {}
const value = getPriceImpact(
[scaledInputs.scaledLowTotal, scaledInputs.scaledHighTotal],
estimatedOutputRange.value,
lpPriceAdjustment,
route === Route.Redeem,
)
return { value }
}, [estimatedOutputRange.fetching, estimatedOutputRange.value, scaledInputs, lpPriceAdjustment, route])
const [update] = useDebounce(
() => {
if (!contract || Object.values(scaledInputs.values).length === 0) return {}
setEstimatedOutputRange.fetching()
const addresses = Object.keys(scaledInputs.values)
const lowAmounts = addresses.map(address => scaledInputs.values[address].low.exact)
const highAmounts = addresses.map(address => scaledInputs.values[address].high.exact)
const paths = ((): Promise<BigNumber>[] => {
switch (route) {
case Route.Mint: {
const outputLow = contract.getMintMultiOutput(addresses, lowAmounts)
const outputHigh = contract.getMintMultiOutput(addresses, highAmounts)
return [outputLow, outputHigh]
}
case Route.Redeem: {
const outputLow = contract.getRedeemExactBassetsOutput(addresses, lowAmounts)
const outputHigh = contract.getRedeemExactBassetsOutput(addresses, highAmounts)
return [outputLow, outputHigh]
}
default:
return []
}
})()
Promise.all(paths)
.then(data => {
const [_low, _high] = data
const low = new BigDecimal(_low)
const high = new BigDecimal(_high)
setEstimatedOutputRange.value([low, high])
})
.catch((_error: Error): void => {
setEstimatedOutputRange.error(sanitizeMassetError(_error))
})
},
2500,
[contract, scaledInputs],
)
const amountIsSet = scaledInputs.highTotal.exact.gt(0)
useEffect(() => {
if (!contract) return
if (amountIsSet) {
setEstimatedOutputRange.fetching()
update()
} else {
setEstimatedOutputRange.value()
}
}, [contract, setEstimatedOutputRange, amountIsSet, update])
return useMemo(
() => ({
estimatedOutputAmount: {
fetching: estimatedOutputRange.fetching,
error: estimatedOutputRange.error,
value: estimatedOutputRange.value?.[1],
},
priceImpact,
}),
[estimatedOutputRange, priceImpact],
)
}
Example #14
Source File: useSaveOutput.ts From mStable-apps with GNU Lesser General Public License v3.0 | 4 votes |
useSaveOutput = (route?: SaveRoutes, inputAddress?: string, inputAmount?: BigDecimal): FetchState<SaveOutput> => {
const [saveOutput, setSaveOutput] = useFetchState<SaveOutput>()
const networkAddresses = useNetworkAddresses()
const networkPrices = useNetworkPrices()
const nativeTokenPriceSimple = networkPrices.value?.nativeToken
const selectedMassetPrice = useSelectedMassetPrice()
const signer = useSigner() as Signer
const massetState = useSelectedMassetState() as MassetState
const {
address: massetAddress,
bAssets,
feederPools,
savingsContracts: {
v2: { address: saveAddress, latestExchangeRate: { rate: latestExchangeRate } = {} },
},
} = massetState
const inputToken = useTokenSubscription(inputAddress)
const outputToken = useTokenSubscription(saveAddress)
const inputRatios = useMassetInputRatios()
const scaledInput = useScaledInput(
{ ...inputToken, amount: inputAmount } as BigDecimalInputValue,
{ ...outputToken, amount: BigDecimal.ZERO },
inputRatios,
)
const scaledInputJSON = toScaledInputJSON(scaledInput)
const feederPoolAddress = inputAddress && Object.values(feederPools).find(fp => fp.fasset.address === inputAddress)?.address
const [update] = useDebounce(
() => {
if (!scaledInputJSON || !inputAddress || !signer) return setSaveOutput.value()
const _scaledInput = fromScaledInputJSON(scaledInputJSON)
if (
!latestExchangeRate ||
!networkAddresses ||
!nativeTokenPriceSimple ||
!selectedMassetPrice.value ||
((route === SaveRoutes.SwapAndSave || route === SaveRoutes.SwapAndStake) && !feederPoolAddress)
) {
return setSaveOutput.fetching()
}
let promise: Promise<SaveOutput>
switch (route) {
case SaveRoutes.Save:
case SaveRoutes.Stake:
case SaveRoutes.SaveAndStake:
promise = Promise.resolve({
amount: _scaledInput.high,
})
break
case SaveRoutes.BuyAndSave:
case SaveRoutes.BuyAndStake:
promise = (async () => {
const [{ amount: low }, { amount: high, path, amountOut }] = await Promise.all([
getOptimalBasset(signer, networkAddresses, massetAddress, bAssets, _scaledInput.low.exact),
getOptimalBasset(signer, networkAddresses, massetAddress, bAssets, _scaledInput.high.exact),
])
const nativeTokenPrice = BigDecimal.fromSimple(nativeTokenPriceSimple).exact
const massetPrice = BigDecimal.fromSimple(selectedMassetPrice.value)
const buyLow = _scaledInput.scaledLow.mulTruncate(nativeTokenPrice).divPrecisely(massetPrice)
const buyHigh = _scaledInput.scaledHigh.mulTruncate(nativeTokenPrice).divPrecisely(massetPrice)
const priceImpact = getPriceImpact([buyLow, buyHigh], [low, high])
return {
amount: high,
amountOut,
path,
priceImpact,
}
})()
break
case SaveRoutes.MintAndSave:
case SaveRoutes.MintAndStake:
promise = (async () => {
const contract = Masset__factory.connect(massetAddress, signer)
const [_low, _high] = await Promise.all([
contract.getMintOutput(inputAddress, _scaledInput.low.scale(scaledInput.high.decimals).exact),
contract.getMintOutput(inputAddress, _scaledInput.high.exact),
])
const low = new BigDecimal(_low)
const high = new BigDecimal(_high)
const priceImpact = getPriceImpact([_scaledInput.scaledLow, _scaledInput.scaledHigh], [low, high])
return {
amount: high,
priceImpact,
}
})()
break
case SaveRoutes.SwapAndSave:
case SaveRoutes.SwapAndStake:
promise = (async () => {
const contract = FeederPool__factory.connect(feederPoolAddress as string, signer)
const [_low, _high] = await Promise.all([
contract.getSwapOutput(inputAddress, massetAddress, _scaledInput.low.scale(scaledInput.high.decimals).exact),
contract.getSwapOutput(inputAddress, massetAddress, _scaledInput.high.exact),
])
const low = new BigDecimal(_low)
const high = new BigDecimal(_high)
const priceImpact = getPriceImpact([_scaledInput.scaledLow, _scaledInput.scaledHigh], [low, high])
return {
amount: high,
priceImpact,
}
})()
break
default:
return setSaveOutput.value()
}
setSaveOutput.fetching()
return promise
.then((output): void => {
setSaveOutput.value(output)
})
.catch((_error: Error): void => {
setSaveOutput.error(sanitizeMassetError(_error))
})
},
1000,
[scaledInputJSON, inputAddress, massetAddress, feederPoolAddress],
)
useEffect(() => {
if (inputAmount?.exact.gt(0) && inputAddress) {
setSaveOutput.fetching()
update()
} else {
setSaveOutput.value()
}
}, [inputAddress, inputAmount, setSaveOutput, update])
return saveOutput
}