Skip to content

Commit

Permalink
feat(dcellar-web-ui): support create on chain folder
Browse files Browse the repository at this point in the history
  • Loading branch information
aiden-cao committed Mar 21, 2024
1 parent 4eb3a6b commit 04639cc
Show file tree
Hide file tree
Showing 8 changed files with 190 additions and 56 deletions.
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { IconFont } from '@/components/IconFont';
import styled from '@emotion/styled';
import { Box, Flex, Text } from '@node-real/uikit';
import { PropsWithChildren, memo } from 'react';
import { PropsWithChildren, memo, ReactNode } from 'react';

interface ListEmptyProps extends PropsWithChildren {
empty: boolean;
title: string;
desc: string;
desc: ReactNode;
type: string;
h?: number;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
PopoverContent,
PopoverContentProps,
PopoverTrigger,
Portal,
} from '@node-real/uikit';
import { TransferEntry } from './TransferEntry';
import { Address } from './Address';
Expand All @@ -21,11 +22,13 @@ export const Account = () => {
<Address address={loginAccount} />
</Box>
</PopoverTrigger>
<PopContent>
<Balance address={loginAccount} />
<TransferEntry />
<OperationEntry />
</PopContent>
<Portal>
<PopContent>
<Balance address={loginAccount} />
<TransferEntry />
<OperationEntry />
</PopContent>
</Portal>
</Popover>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ import {
} from '@node-real/uikit';
import { useAsyncEffect, useUnmount } from 'ahooks';
import BigNumber from 'bignumber.js';
import { isEmpty } from 'lodash-es';
import { isEmpty, last, trimEnd } from 'lodash-es';
import { ChangeEvent, memo, useCallback, useEffect, useMemo, useState } from 'react';
import { useAccount } from 'wagmi';
import { TotalFees } from './TotalFees';
Expand All @@ -73,13 +73,15 @@ interface CreateFolderOperationProps {
selectBucket: TBucket;
bucketAccountDetail: AccountInfo;
primarySp: SpEntity;
chainFolder?: string;
refetch?: (name?: string) => void;
onClose?: () => void;
}

export const CreateFolderOperation = memo<CreateFolderOperationProps>(function CreateFolderDrawer({
refetch = () => {},
onClose = () => {},
chainFolder: chainFolderName,
selectBucket: bucket,
bucketAccountDetail: accountDetail,
primarySp,
Expand All @@ -102,7 +104,8 @@ export const CreateFolderOperation = memo<CreateFolderOperationProps>(function C
const { settlementFee } = useSettlementFee(PaymentAddress);
const [balanceEnough, setBalanceEnough] = useState(true);
const [loading, setLoading] = useState(false);
const [inputFolderName, setInputFolderName] = useState('');
const initFolderName = last(trimEnd(chainFolderName || '', '/').split('/'));
const [inputFolderName, setInputFolderName] = useState(initFolderName || '');
const [formErrors, setFormErrors] = useState<string[]>([]);
const [usedNames, setUsedNames] = useState<string[]>([]);

Expand Down Expand Up @@ -288,7 +291,7 @@ export const CreateFolderOperation = memo<CreateFolderOperationProps>(function C
errors.push('Cannot consist of slash(/).');
}
const folderNames = folderList.map((folder) => folder.name);
if (folderNames.includes(value)) {
if (folderNames.includes(value) && !chainFolderName) {
errors.push('Folder name already exists.');
}
setFormErrors(errors);
Expand Down Expand Up @@ -382,7 +385,7 @@ export const CreateFolderOperation = memo<CreateFolderOperationProps>(function C
return (
<>
<QDrawerHeader flexDirection={'column'}>
<Box>Create a Folder</Box>
<Box>{chainFolderName ? 'Create on chain folder' : 'Create a Folder'}</Box>
<Text className="ui-drawer-sub">
Use folders to group objects in your bucket. Folder names can&apos;t contain
&quot;/&quot;.
Expand All @@ -396,6 +399,7 @@ export const CreateFolderOperation = memo<CreateFolderOperationProps>(function C
Name
</Text>
<InputItem
disabled={!!chainFolderName}
onKeyDown={(e) => e.key === 'Enter' && onCreateFolder()}
value={inputFolderName}
onChange={onFolderNameChange}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export const CreateObject = memo<NewObjectProps>(function NewObject({
</>
);

const folderExist = !objectCommonPrefix ? true : !!objectRecords[completeCommonPrefix + '/'];
const folderExist = !objectCommonPrefix || !!objectRecords[completeCommonPrefix + '/'];

const invalidPath =
pathSegments.some((name) => new Blob([name]).size > MAX_FOLDER_NAME_LEN) || !folderExist;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,15 @@ import { convertObjectKey } from '@/utils/common';
import { formatId } from '@/utils/string';
import { formatFullTime } from '@/utils/time';
import { ResourceTags_Tag } from '@bnb-chain/greenfield-cosmos-types/greenfield/storage/types';
import { Divider, Flex, QDrawerBody, QDrawerHeader, Text } from '@node-real/uikit';
import { Box, Divider, Flex, QDrawerBody, QDrawerHeader, Text } from '@node-real/uikit';
import { useMount, useUnmount } from 'ahooks';
import { last } from 'lodash-es';
import { memo } from 'react';
import { memo, useMemo, useState } from 'react';
import { MOCK_EMPTY_FOLDER_OBJECT } from '@/modules/object/constant';
import { ObjectMeta } from '@bnb-chain/greenfield-js-sdk/dist/esm/types/sp/Common';
import { DCLink } from '@/components/common/DCLink';
import { Tips } from '@/components/common/Tips';
import { DCButton } from '@/components/common/DCButton';

interface DetailFolderOperationProps {
objectName: string;
Expand All @@ -32,13 +37,28 @@ export const DetailFolderOperation = memo<DetailFolderOperationProps>(
const dispatch = useAppDispatch();
const completeCommonPrefix = useAppSelector((root) => root.object.completeCommonPrefix);
const objectRecords = useAppSelector((root) => root.object.objectRecords);
// const objectOperation = useAppSelector((root) => root.object.objectOperation);
// const [id, operation, params] = objectOperation[1];
// const preOperation = usePrevious(operation);
// const preObjectName = usePrevious(params?.objectName);

const selectObjectInfo = useModalValues(
objectRecords[[selectBucket.BucketName, objectName].join('/')] || {},
const cacheKey = [selectBucket.BucketName, objectName].join('/');
const mockMeta: ObjectMeta = useMemo(
() => ({
...MOCK_EMPTY_FOLDER_OBJECT,
ObjectInfo: {
...MOCK_EMPTY_FOLDER_OBJECT.ObjectInfo,
ObjectName: objectName,
BucketName: selectBucket.BucketName,
},
}),
[objectName, selectBucket.BucketName],
);
const selectObjectInfo = useModalValues(objectRecords[cacheKey] || mockMeta);
const objectInfo = useModalValues(selectObjectInfo.ObjectInfo);
const [folderExist, setFolderExist] = useState(true);
const folderName = last(objectName.replace(/\/$/, '').split('/'));
const loading = !objectInfo;
const loading = !(cacheKey in objectRecords) && folderExist;

const onEditTags = () => {
const lowerKeyTags = selectObjectInfo.ObjectInfo?.Tags?.Tags.map((item) =>
Expand All @@ -53,7 +73,7 @@ export const DetailFolderOperation = memo<DetailFolderOperationProps>(
);
};

useMount(async () => {
const getFolderObjectList = async () => {
const _query = new URLSearchParams();
_query.append('delimiter', '/');
_query.append('maxKeys', '2');
Expand All @@ -70,16 +90,23 @@ export const DetailFolderOperation = memo<DetailFolderOperationProps>(

const [res, error] = await getListObjects(params);
// should never happen
if (error || !res || res.code !== 0) return false;
if (error || !res || res.code !== 0) return;
const { GfSpListObjectsByBucketNameResponse } = res.body!;
const list = GfSpListObjectsByBucketNameResponse!;
// 更新文件夹objectInfo
dispatch(
setObjectList({
path: completeCommonPrefix,
list: GfSpListObjectsByBucketNameResponse || [],
infoOnly: true,
}),
);
dispatch(setObjectList({ path: completeCommonPrefix, list, infoOnly: true }));
return list;
};

// useAsyncEffect(async () => {
// if (preObjectName !== objectInfo.ObjectName || preOperation !== 'create_folder') return;
// await getFolderObjectList();
// }, [preOperation, preObjectName, objectInfo.ObjectName]);

useMount(async () => {
const list = await getFolderObjectList();
if (!list) return;
setFolderExist(list.Objects[0].ObjectInfo.ObjectName.endsWith('/'));
});

useUnmount(() => dispatch(setObjectEditTagsData([DEFAULT_TAG])));
Expand All @@ -101,13 +128,64 @@ export const DetailFolderOperation = memo<DetailFolderOperationProps>(
>
{folderName}
</Text>
<Text fontSize={14} fontWeight={500} color={'readable.tertiary'}>
--
<Text as={'div'} fontSize={14} fontWeight={500} color={'readable.tertiary'}>
{folderExist ? (
'--'
) : (
<Flex alignItems={'center'}>
This is a folder simulated by a path.{' '}
<Tips
placement={'bottom'}
containerWidth={'220px'}
tips={
<Box fontSize={'12px'} lineHeight="14px" w={'200px'}>
<Box>
{
"This path doesn't exist as an entity on the blockchain and lacks chain information."
}
</Box>
<DCLink
href="https://docs.nodereal.io/docs/dcellar-faq#question-what-is--folder-simulated-by-a-path-"
target="_blank"
>
Learn more
</DCLink>
</Box>
}
/>
</Flex>
)}
</Text>
</Flex>
</Flex>
<Divider />
<Flex my={8} gap={8} flexDirection={'column'}>
<Flex position={'relative'} my={8} gap={8} flexDirection={'column'}>
{!folderExist && (
<Box
position={'absolute'}
w={310}
h={'100%'}
right={0}
top={0}
bg={'rgba(255, 255, 255, 0.70)'}
backdropFilter={'blur(2px)'}
>
<DCButton
onClick={() =>
dispatch(
setObjectOperation({
operation: ['', 'create_folder', { objectName: objectInfo.ObjectName }],
}),
)
}
position={'absolute'}
right={24}
top={70}
>
Create on chain folder
</DCButton>
</Box>
)}
{renderPropRow(
'Date created',
loading ? '' : formatFullTime(+objectInfo.CreateAt * 1000),
Expand Down
60 changes: 33 additions & 27 deletions apps/dcellar-web-ui/src/modules/object/components/ObjectList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,14 @@ import { formatBytes } from '@/utils/formatter';
import { pickAction, removeAction } from '@/utils/object';
import { apolloUrlTemplate } from '@/utils/string';
import { formatTime, getMillisecond } from '@/utils/time';
import { Flex } from '@node-real/uikit';
import { Box, Flex } from '@node-real/uikit';
import { useAsyncEffect, useUpdateEffect } from 'ahooks';
import { ColumnProps } from 'antd/es/table';
import dayjs from 'dayjs';
import { find, uniq, without, xor } from 'lodash-es';
import { memo, useCallback } from 'react';
import { OBJECT_ERROR_TYPES, ObjectErrorType } from '../ObjectError';
import Link from 'next/link';
// import { ManageObjectTagsDrawer } from './ManageObjectTagsDrawer';

export type ObjectActionValueType =
Expand Down Expand Up @@ -127,6 +128,7 @@ export const ObjectList = memo<ObjectListProps>(function ObjectList({ shareMode
const bucket = bucketRecords[currentBucketName];
const accountDetail = useAppSelector(selectAccount(bucket?.PaymentAddress));

const currentPathExist = !objectCommonPrefix || !!objectRecords[completeCommonPrefix + '/'];
const filtered =
!!objectNameFilter.trim() ||
objectTypeFilter.length ||
Expand Down Expand Up @@ -197,33 +199,37 @@ export const ObjectList = memo<ObjectListProps>(function ObjectList({ shareMode

const empty = !loading && !sortedList.length;
const loadingComponent = { spinning: loading, indicator: <Loading /> };
const renderEmpty = useCallback(
() => (
<ListEmpty
type={isBucketDiscontinue && !filtered ? 'discontinue' : 'empty-object'}
title={
filtered || shareMode
? 'No Results'
: isBucketDiscontinue
? 'Discontinue Notice'
: 'Upload Objects and Start Your Work Now'
}
desc={
filtered || shareMode
? 'No results found. Please try different conditions.'
: isBucketDiscontinue
? 'This bucket were marked as discontinued and will be deleted by SP soon. '
: `To avoid data loss during testnet phase, the file size should not exceed ${formatBytes(
SINGLE_OBJECT_MAX_SIZE,
)}.`
}
empty={empty}
>
{!filtered && !shareMode && <CreateObject showRefresh={false} />}
const renderEmpty = useCallback(() => {
const type = isBucketDiscontinue && !filtered ? 'discontinue' : 'empty-object';
const title = (() => {
if (filtered || shareMode) return 'No Results';
if (isBucketDiscontinue) return 'Discontinue Notice';
if (!currentPathExist) return 'No Objects Under This Path';
return 'Upload Objects and Start Your Work Now';
})();

const desc = (() => {
if (filtered || shareMode) return 'No results found. Please try different conditions.';
if (isBucketDiscontinue)
return 'This bucket were marked as discontinued and will be deleted by SP soon. ';
if (!currentPathExist)
return (
<Box sx={{ a: { color: 'brand.normal' } }}>
The path no longer exists on DCellar. You can{' '}
<Link href={`/buckets/${currentBucketName}`}>return to the bucket list</Link> and
continue your work.
</Box>
);
return `To avoid data loss during testnet phase, the file size should not exceed ${formatBytes(
SINGLE_OBJECT_MAX_SIZE,
)}.`;
})();
return (
<ListEmpty type={type} title={title} desc={desc} empty={empty}>
{!filtered && !shareMode && currentPathExist && <CreateObject showRefresh={false} />}
</ListEmpty>
),
[isBucketDiscontinue, empty, filtered, shareMode],
);
);
}, [isBucketDiscontinue, empty, filtered, shareMode, currentPathExist, currentBucketName]);

const columns: ColumnProps<ObjectEntity>[] = [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ export const ObjectOperations = memo<ObjectOperationsProps>(function ObjectOpera
case 'create_folder':
return (
<CreateFolderOperation
chainFolder={params?.objectName}
selectBucket={selectBucket}
bucketAccountDetail={bucketAccountDetail}
primarySp={primarySp}
Expand Down
Loading

0 comments on commit 04639cc

Please sign in to comment.