Skip to content

Commit

Permalink
Merge pull request #368 from node-real/feat/folder-path-on-chain
Browse files Browse the repository at this point in the history
feat(dcellar-web-ui): support create on chain folder
  • Loading branch information
aiden-cao authored Mar 25, 2024
2 parents 4eb3a6b + d76751d commit 5835eaa
Show file tree
Hide file tree
Showing 15 changed files with 268 additions and 67 deletions.
12 changes: 12 additions & 0 deletions apps/dcellar-web-ui/CHANGELOG.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
{
"name": "dcellar-web-ui",
"entries": [
{
"version": "0.4.0",
"tag": "dcellar-web-ui_v0.4.0",
"date": "Mon, 25 Mar 2024 04:02:13 GMT",
"comments": {
"minor": [
{
"comment": "Support create on chain folder & share virtual path"
}
]
}
},
{
"version": "0.3.1",
"tag": "dcellar-web-ui_v0.3.1",
Expand Down
9 changes: 8 additions & 1 deletion apps/dcellar-web-ui/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
# Change Log - dcellar-web-ui

This log was last generated on Wed, 20 Mar 2024 07:14:31 GMT and should not be manually modified.
This log was last generated on Mon, 25 Mar 2024 04:02:13 GMT and should not be manually modified.

## 0.4.0
Mon, 25 Mar 2024 04:02:13 GMT

### Minor changes

- Support create on chain folder & share virtual path

## 0.3.1
Wed, 20 Mar 2024 07:14:31 GMT
Expand Down
2 changes: 1 addition & 1 deletion apps/dcellar-web-ui/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dcellar-web-ui",
"version": "0.3.1",
"version": "0.4.0",
"private": false,
"scripts": {
"dev": "node ./scripts/dev.js -p 3200",
Expand Down
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,10 +385,16 @@ 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;.
{chainFolderName ? (
'Convert your existing path to an on chain folder to view detailed data on the chain and obtain additional features.'
) : (
<>
Use folders to group objects in your bucket. Folder names can&apos;t contain
&quot;/&quot;.
</>
)}
</Text>
</QDrawerHeader>
<QDrawerBody>
Expand All @@ -396,6 +405,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 @@ -117,7 +117,8 @@ export const DeleteObjectOperation = memo<DeleteObjectOperationProps>(
);
return (
GfSpListObjectsByBucketNameResponse.KeyCount === '1' &&
GfSpListObjectsByBucketNameResponse.Objects[0].ObjectInfo.ObjectName === objectName
// virtual path
GfSpListObjectsByBucketNameResponse.Objects[0]?.ObjectInfo.ObjectName === objectName
);
};

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,24 @@ 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;
// virtual path
setFolderExist(list.Objects[0]?.ObjectInfo.ObjectName.endsWith('/') || false);
});

useUnmount(() => dispatch(setObjectEditTagsData([DEFAULT_TAG])));
Expand All @@ -101,13 +129,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
Loading

0 comments on commit 5835eaa

Please sign in to comment.