diff --git a/apps/dcellar-web-ui/public/images/icons/refresh.svg b/apps/dcellar-web-ui/public/images/icons/refresh.svg new file mode 100644 index 00000000..541365c5 --- /dev/null +++ b/apps/dcellar-web-ui/public/images/icons/refresh.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/apps/dcellar-web-ui/src/modules/object/components/CreateFolder.tsx b/apps/dcellar-web-ui/src/modules/object/components/CreateFolder.tsx index 2ecf95ea..6dc162da 100644 --- a/apps/dcellar-web-ui/src/modules/object/components/CreateFolder.tsx +++ b/apps/dcellar-web-ui/src/modules/object/components/CreateFolder.tsx @@ -54,7 +54,6 @@ import { useChecksumApi } from '@/modules/checksum'; import { resolve } from '@/facade/common'; import { DCDrawer } from '@/components/common/DCDrawer'; import { TStatusDetail, setEditCreate, setStatusDetail } from '@/store/slices/object'; -import { duplicateName } from '@/utils/object'; import { setupTmpAvailableBalance } from '@/store/slices/global'; import { useOffChainAuth } from '@/hooks/useOffChainAuth'; import { getObjectMeta } from '@/facade/object'; @@ -235,7 +234,8 @@ export const CreateFolder = memo(function CreateFolderDrawer({ refet if (value.includes('/')) { errors.push('Cannot consist of slash(/).'); } - if (duplicateName(value, folderList)) { + const folderNames = folderList.map((folder) => folder.name); + if (folderNames.includes(value)) { errors.push('Folder name already exists.'); } setFormErrors(errors); diff --git a/apps/dcellar-web-ui/src/modules/object/components/NewObject.tsx b/apps/dcellar-web-ui/src/modules/object/components/NewObject.tsx index 4dd7b4b6..27dd1336 100644 --- a/apps/dcellar-web-ui/src/modules/object/components/NewObject.tsx +++ b/apps/dcellar-web-ui/src/modules/object/components/NewObject.tsx @@ -1,13 +1,24 @@ import React, { ChangeEvent, memo } from 'react'; import { useAppDispatch, useAppSelector } from '@/store'; import { GAClick } from '@/components/common/GATracker'; -import { Button, Flex, Menu, MenuButton, MenuItem, MenuList, Text, Tooltip } from '@totejs/uikit'; +import { + Box, + Button, + Flex, + Menu, + MenuButton, + MenuItem, + MenuList, + Text, + Tooltip, +} from '@totejs/uikit'; import UploadIcon from '@/public/images/files/upload_transparency.svg'; -import { setEditCreate, setEditUploadStatus } from '@/store/slices/object'; +import { setEditCreate, setEditUploadStatus, setListRefreshing, setRestoreCurrent, setupListObjects } from '@/store/slices/object'; import { addToWaitQueue } from '@/store/slices/global'; import { getUtcZeroTimestamp } from '@bnb-chain/greenfield-chain-sdk'; import { MenuCloseIcon, MenuOpenIcon } from '@totejs/icons'; - +import RefreshIcon from '@/public/images/icons/refresh.svg'; +import { getSpOffChainData } from '@/store/slices/persist'; interface NewObjectProps { gaFolderClickName?: string; gaUploadClickName?: string; @@ -22,7 +33,10 @@ export const NewObject = memo(function NewObject({ }) { const dispatch = useAppDispatch(); const { discontinue, owner } = useAppSelector((root) => root.bucket); - const { folders, prefix, path, objectsInfo } = useAppSelector((root) => root.object); + const { folders, prefix, path, objectsInfo, bucketName} = useAppSelector((root) => root.object); + const { loginAccount } = useAppSelector((root) => root.persist); + const { primarySpInfo } = useAppSelector((root) => root.sp); + const primarySp = primarySpInfo[bucketName]; const onOpenCreateFolder = () => { if (disabled) return; dispatch(setEditCreate(true)); @@ -53,8 +67,27 @@ export const NewObject = memo(function NewObject({ e.target.value = ''; }; + const refreshList = async () => { + const { seedString } = await dispatch( + getSpOffChainData(loginAccount, primarySp.operatorAddress), + ); + const query = new URLSearchParams(); + const params = { + seedString, + query, + endpoint: primarySp.endpoint, + }; + dispatch(setListRefreshing(true)); + dispatch(setRestoreCurrent(false)); + await dispatch(setupListObjects(params)); + dispatch(setListRefreshing(false)); + } + return ( + refreshList()} alignItems='center' height={40} marginRight={12} cursor='pointer'> + + { ); const selectedFiles = waitQueue; const objectList = objects[path]?.filter((item) => !item.objectName.endsWith('/')); + const {uploadQueue} = useAppSelector((root) => root.global); const [creating, setCreating] = useState(false); const { tabOptions, activeKey, setActiveKey } = useTab(); @@ -110,7 +110,9 @@ export const UploadObjects = () => { if (file.name.includes('//')) { return E_OBJECT_NAME_CONTAINS_SLASH; } - if (duplicateName(file.name, objectList)) { + const objectListNames = objectList.map((item) => item.objectName); + const uploadingNames = (uploadQueue?.[loginAccount] || []).map((item) => item.file.name); + if ([...objectListNames, ...uploadingNames].includes(file.name)) { return E_OBJECT_NAME_EXISTS; } return ''; diff --git a/apps/dcellar-web-ui/src/store/slices/global.ts b/apps/dcellar-web-ui/src/store/slices/global.ts index a2c59479..281e70a5 100644 --- a/apps/dcellar-web-ui/src/store/slices/global.ts +++ b/apps/dcellar-web-ui/src/store/slices/global.ts @@ -301,7 +301,6 @@ export const setupPreLockFeeObjects = (primarySpAddress: string) => async (dispa } = (storageParams && storageParams.versionedParams) || {}; const { params: paymentParams } = await client.payment.params(); const { reserveTime, validatorTaxRate } = paymentParams?.versionedParams || {}; - const lockFeeParamsPayload = { spStorageStorePrice: spStoragePrice?.storePrice || '', secondarySpStorePrice: secondarySpStoragePrice?.storePrice || '', diff --git a/apps/dcellar-web-ui/src/store/slices/object.ts b/apps/dcellar-web-ui/src/store/slices/object.ts index 3fba49a9..83d68724 100644 --- a/apps/dcellar-web-ui/src/store/slices/object.ts +++ b/apps/dcellar-web-ui/src/store/slices/object.ts @@ -55,6 +55,7 @@ export interface ObjectState { statusDetail: TStatusDetail; editUpload: TEditUpload; deletedObjects: Record; + refreshing: boolean; } const initialState: ObjectState = { @@ -76,6 +77,7 @@ const initialState: ObjectState = { statusDetail: {} as TStatusDetail, editUpload: {} as TEditUpload, deletedObjects: {}, + refreshing: false, }; export const objectSlice = createSlice({ @@ -242,6 +244,9 @@ export const objectSlice = createSlice({ state.objectsMeta[path] = omit(list, ['objects', 'common_prefixes']); state.objects[path] = folders.concat(objects as ObjectItem[]); }, + setListRefreshing(state, { payload }: PayloadAction) { + state.refreshing = payload; + } }, }); @@ -296,10 +301,6 @@ export const setupListObjects = const { prefix, bucketName, path, restoreCurrent } = getState().object; const { loginAccount: address } = getState().persist; - dispatch(setRestoreCurrent(true)); - if (!restoreCurrent) { - dispatch(setCurrentObjectPage({ path, current: 0 })); - } const _query = new URLSearchParams(params.query?.toString() || ''); _query.append('max-keys', '1000'); _query.append('delimiter', '/'); @@ -314,13 +315,18 @@ export const setupListObjects = return; } dispatch(setObjectList({ path: _path || path, list: res! })); + dispatch(setRestoreCurrent(true)); + if (!restoreCurrent) { + dispatch(setCurrentObjectPage({ path, current: 0 })); + } }; + export const closeStatusDetail = () => async (dispatch: AppDispatch) => { dispatch(setStatusDetail({} as TStatusDetail)); }; export const selectPathLoading = (root: AppState) => { - const { objects, path } = root.object; - return !(path in objects); + const { objects, path, refreshing } = root.object; + return !(path in objects) || refreshing; }; export const selectPathCurrent = (root: AppState) => { @@ -352,6 +358,7 @@ export const { setDummyFolder, updateObjectVisibility, addDeletedObject, + setListRefreshing, } = objectSlice.actions; export default objectSlice.reducer; diff --git a/apps/dcellar-web-ui/src/utils/object/index.ts b/apps/dcellar-web-ui/src/utils/object/index.ts index 2c7ec46b..1df7846a 100644 --- a/apps/dcellar-web-ui/src/utils/object/index.ts +++ b/apps/dcellar-web-ui/src/utils/object/index.ts @@ -6,10 +6,6 @@ export type TReverseVisibilityType = { [K in number]: TKey; }; -export const duplicateName = (name: string, objects: ObjectItem[]) => { - return objects.some((item) => item.name === name); -} - const StringIsNumber = (value: string) => isNaN(Number(value)) === false; export const convertVisibility = () => { diff --git a/apps/dcellar-web-ui/src/utils/sp/index.ts b/apps/dcellar-web-ui/src/utils/sp/index.ts index e9e35da4..9e69c5cc 100644 --- a/apps/dcellar-web-ui/src/utils/sp/index.ts +++ b/apps/dcellar-web-ui/src/utils/sp/index.ts @@ -53,21 +53,15 @@ export const calPreLockFee = ({ size, preLockFeeObject }: { size: number; primar } = preLockFeeObject; const chargeSize = size >= minChargeSize ? size : minChargeSize; - const lockedFeeRate = BigNumber(spStorageStorePrice) - .plus( - BigNumber(secondarySpStorePrice).times( - redundantDataChunkNum + redundantParityChunkNum, - ), - ) - .times(BigNumber(chargeSize)) - .times(BigNumber(validatorTaxRate).dividedBy(Math.pow(10, 18))) - .dividedBy(Math.pow(10, 18)); - const lockFeeInBNB = lockedFeeRate - .times(BigNumber(reserveTime || 0)) - .dividedBy(Math.pow(10, 18)); + const primarySpRate = BigNumber(spStorageStorePrice).dividedBy(Math.pow(10, 18)).times(BigNumber(chargeSize)); + const secondarySpNum = redundantDataChunkNum + redundantParityChunkNum; + let secondarySpRate = BigNumber(secondarySpStorePrice).dividedBy(Math.pow(10, 18)).times(BigNumber(chargeSize)); + secondarySpRate = secondarySpRate.times(secondarySpNum); + const validatorTax = BigNumber(validatorTaxRate).dividedBy(Math.pow(10, 18)).times(primarySpRate.plus(secondarySpRate)); + const rate = primarySpRate.plus(secondarySpRate).plus(validatorTax); + const lockFeeInBNB = rate.times(BigNumber(reserveTime || 0)).dividedBy(Math.pow(10, 18)); return lockFeeInBNB.toString() - } const checkZkWasm = (attempts: number = 5): Promise=> { return new Promise((resolve) => {