Skip to content

Commit

Permalink
feat(dcellar-web-ui): add multi download
Browse files Browse the repository at this point in the history
  • Loading branch information
aiden-cao committed Aug 4, 2023
1 parent bb0d041 commit f4c1831
Show file tree
Hide file tree
Showing 12 changed files with 168 additions and 99 deletions.
55 changes: 26 additions & 29 deletions apps/dcellar-web-ui/src/facade/object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
import {
broadcastFault,
commonFault,
downloadPreviewFault,
E_NO_QUOTA,
E_NOT_FOUND,
E_PERMISSION_DENIED,
Expand All @@ -32,19 +31,16 @@ import { VisibilityType } from '@bnb-chain/greenfield-cosmos-types/greenfield/st
import { quotaRemains } from '@/facade/bucket';
import { ObjectInfo } from '@bnb-chain/greenfield-cosmos-types/greenfield/storage/types';
import { encodeObjectName } from '@/utils/string';
import {
downloadWithProgress,
saveFileByAxiosResponse,
viewFileByAxiosResponse,
} from '@/modules/file/utils';
import axios, { AxiosResponse } from 'axios';
import axios from 'axios';
import { SpItem } from '@/store/slices/sp';
import {
QueryHeadObjectResponse,
QueryLockFeeRequest,
} from '@bnb-chain/greenfield-cosmos-types/greenfield/storage/query';
import { signTypedDataV4 } from '@/utils/signDataV4';
import BigNumber from 'bignumber.js';
import { getDomain } from '@/utils/getDomain';
import { generateGetObjectOptions } from '@/modules/file/utils/generateGetObjectOptions';
import { batchDownload, directlyDownload } from '@/modules/file/utils';

export type DeliverResponse = Awaited<ReturnType<TxResponse['broadcast']>>;

Expand Down Expand Up @@ -101,29 +97,30 @@ export const getCanObjectAccess = async (
};

export type DownloadPreviewParams = {
objectInfo: ObjectInfo;
objectInfo: { bucketName: string; objectName: string; visibility: number };
primarySp: SpItem;
address: string;
};

const getObjectBytes = async (
export const getAuthorizedLink = async (
params: DownloadPreviewParams,
seedString: string,
): Promise<[AxiosResponse | null, ErrorMsg?]> => {
view = 1,
): Promise<[null, ErrorMsg] | [string]> => {
const { address, primarySp, objectInfo } = params;
const { bucketName, objectName, payloadSize } = objectInfo;

const [result, error] = await downloadWithProgress({
const { bucketName, objectName } = objectInfo;
const domain = getDomain();
const [options, error] = await generateGetObjectOptions({
bucketName,
objectName,
primarySp,
payloadSize: payloadSize.toNumber(),
address,
endpoint: primarySp.endpoint,
userAddress: address,
domain,
seedString,
}).then(resolve, downloadPreviewFault);
if (!result) return [null, error];

return [result];
}).then(resolve, commonFault);
if (error) return [null, error];
const { url, params: _params } = options!;
return [`${url}?${_params}&view=${view}`];
};

export const downloadObject = async (
Expand All @@ -137,14 +134,14 @@ export const downloadObject = async (
const isPrivate = visibility === VisibilityType.VISIBILITY_TYPE_PRIVATE;
const link = `${endpoint}/download/${bucketName}/${encodeObjectName(objectName)}`;
if (!isPrivate) {
window.location.href = link;
batchDownload(link);
return [true];
}

const [result, error] = await getObjectBytes(params, seedString);
if (!result) return [false, error];
const [url, error] = await getAuthorizedLink(params, seedString, 0);
if (!url) return [false, error];

saveFileByAxiosResponse(result, objectName);
batchDownload(url);
return [true];
};

Expand All @@ -159,14 +156,14 @@ export const previewObject = async (
const isPrivate = visibility === VisibilityType.VISIBILITY_TYPE_PRIVATE;
const link = `${endpoint}/view/${bucketName}/${encodeObjectName(objectName)}`;
if (!isPrivate) {
window.open(link, '_blank');
directlyDownload(link, '_blank');
return [true];
}

const [result, error] = await getObjectBytes(params, seedString);
if (!result) return [false, error];
const [url, error] = await getAuthorizedLink(params, seedString);
if (!url) return [false, error];

viewFileByAxiosResponse(result);
directlyDownload(url, '_blank');
return [true];
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,9 @@ export const generateGetObjectOptions = async (
});

const params = new URLSearchParams();
// params.append('authorization', body?.authorization || '');
// params.append('user-address', userAddress);
// params.append('app-domain', domain);
// params.append('view', '1');
params.append('authorization', body?.authorization || '');
params.append('user-address', userAddress);
params.append('app-domain', domain);

return {
url,
Expand Down
13 changes: 12 additions & 1 deletion apps/dcellar-web-ui/src/modules/file/utils/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ const renderInsufficientBalance = (
);
};

const directlyDownload = (url: string, name?: string) => {
const directlyDownload = (url: string, target = '_self', name?: string) => {
if (!url) {
toast.error({
description: 'Download url not existed. Please check.',
Expand All @@ -302,11 +302,22 @@ const directlyDownload = (url: string, name?: string) => {
const link = document.createElement('a');
link.href = url;
link.download = name || '';
link.target = target;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};

export const batchDownload = (url: string | string[]) => {
const urls = Array<string>().concat(url);
urls.forEach((url) => {
const iframe = document.createElement('iframe');
iframe.src = url;
iframe.style.display = 'none';
document.body.appendChild(iframe);
});
};

const getQuota = async (
bucketName: string,
endpoint: string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import { Text } from '@totejs/uikit';
import { useAppDispatch, useAppSelector } from '@/store';
import { E_NO_QUOTA, E_OFF_CHAIN_AUTH, E_UNKNOWN } from '@/facade/error';
import { OBJECT_ERROR_TYPES, ObjectErrorType } from '@/modules/object/ObjectError';
import { setStatusDetail } from '@/store/slices/object';
import { setSelectedRowKeys, setStatusDetail } from '@/store/slices/object';
import { useOffChainAuth } from '@/hooks/useOffChainAuth';
import { useMount } from 'ahooks';
import { setupBucketQuota } from '@/store/slices/bucket';
import { quotaRemains } from '@/facade/bucket';
import { getSpOffChainData } from '@/store/slices/persist';
import { downloadObject } from '@/facade/object';

interface BatchOperationsProps {}

Expand All @@ -20,8 +22,10 @@ export const BatchOperations = memo<BatchOperationsProps>(function BatchOperatio
const { setOpenAuthModal } = useOffChainAuth();
const { loginAccount } = useAppSelector((root) => root.persist);
const { bucketName, objects, path } = useAppSelector((root) => root.object);
const { primarySpInfo } = useAppSelector((root) => root.sp);
const quotas = useAppSelector((root) => root.bucket.quotas);
const quotaData = quotas[bucketName];
const primarySp = primarySpInfo[bucketName];

useMount(() => {
dispatch(setupBucketQuota(bucketName));
Expand All @@ -45,10 +49,18 @@ export const BatchOperations = memo<BatchOperationsProps>(function BatchOperatio
items.reduce((x, y) => x + y.payloadSize, 0),
);
if (!remainQuota) return onError(E_NO_QUOTA);
// const operator = primarySp.operatorAddress;
// const { seedString } = await dispatch(getSpOffChainData(loginAccount, operator));
// const domain = getDomain();
// todo
const operator = primarySp.operatorAddress;
const { seedString } = await dispatch(getSpOffChainData(loginAccount, operator));

for (const item of items) {
const payload = {
primarySp,
objectInfo: item,
address: loginAccount,
};
await downloadObject(payload, seedString);
}
dispatch(setSelectedRowKeys([]));
};

return (
Expand Down
10 changes: 7 additions & 3 deletions apps/dcellar-web-ui/src/modules/object/components/ObjectList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ import { CreateFolder } from './CreateFolder';
import { useOffChainAuth } from '@/hooks/useOffChainAuth';
import { StyledRow } from '@/modules/object/objects.style';
import { UploadFile, selectUploadQueue } from '@/store/slices/global';
import { copy, encodeObjectName, getShareLink } from '@/utils/string';
import { toast } from '@totejs/uikit';

const Actions: ActionMenuItem[] = [
{ label: 'View Details', value: 'detail' },
Expand Down Expand Up @@ -86,7 +88,7 @@ export const ObjectList = memo<ObjectListProps>(function ObjectList() {
);
const currentPage = useAppSelector(selectPathCurrent);
const { discontinue, owner } = useAppSelector((root) => root.bucket);
const { primarySpInfo} = useAppSelector((root) => root.sp);
const { primarySpInfo } = useAppSelector((root) => root.sp);
const loading = useAppSelector(selectPathLoading);
const objectList = useAppSelector(selectObjectList);
const { setOpenAuthModal } = useOffChainAuth();
Expand Down Expand Up @@ -181,7 +183,9 @@ export const ObjectList = memo<ObjectListProps>(function ObjectList() {
case 'delete':
return dispatch(setEditDelete(record));
case 'share':
return dispatch(setEditShare(record));
copy(getShareLink(bucketName, record.objectName));
toast.success({ description: 'Successfully copied to your clipboard.' });
return;
case 'download':
return download(record);
case 'cancel':
Expand Down Expand Up @@ -275,7 +279,7 @@ export const ObjectList = memo<ObjectListProps>(function ObjectList() {
// It is not allowed to cancel when the chain is sealed, but the SP is not synchronized.
const file = find<UploadFile>(
uploadQueue,
(q) => [...q.prefixFolders, q.file.name].join('/') === record.objectName
(q) => [...q.prefixFolders, q.file.name].join('/') === record.objectName,
);
if (file) {
fitActions = fitActions.filter((a) => a.value !== 'cancel');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,14 @@ import { GAClick } from '@/components/common/GATracker';
// import { AccessItem } from '@/modules/file/components/AccessItem';
import { useAppDispatch, useAppSelector } from '@/store';
import { ObjectItem, setEditShare } from '@/store/slices/object';
import { encodeObjectName } from '@/utils/string';
import { getShareLink } from '@/utils/string';

interface modalProps {}

export const ShareObject = (props: modalProps) => {
const dispatch = useAppDispatch();
const { hasCopied, onCopy, setValue } = useClipboard('');
const { editShare, bucketName } = useAppSelector((root) => root.object);
const params = [bucketName, encodeObjectName(editShare.objectName)].join('/');
const isOpen = !!editShare.objectName;
const onClose = () => {
dispatch(setEditShare({} as ObjectItem));
Expand All @@ -33,8 +32,8 @@ export const ShareObject = (props: modalProps) => {
};

useEffect(() => {
setValue(`${location.origin}/share?file=${encodeURIComponent(params)}`);
}, [setValue, params]);
setValue(getShareLink(bucketName, editShare.objectName));
}, [setValue, bucketName, editShare.objectName]);

return (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import {
import { useOffChainAuth } from '@/hooks/useOffChainAuth';
import { ViewerList } from '@/modules/object/components/ViewerList';
import { CopyButton } from '@/modules/object/components/CopyButton';
import { encodeObjectName } from '@/utils/string';
import { encodeObjectName, getShareLink } from '@/utils/string';

interface SharePermissionProps {}

Expand Down Expand Up @@ -109,8 +109,6 @@ export const SharePermission = memo<SharePermissionProps>(function SharePermissi
);
};

const params = [bucketName, encodeObjectName(editDetail.objectName)].join('/');

return (
<>
<DCDrawer
Expand Down Expand Up @@ -184,7 +182,7 @@ export const SharePermission = memo<SharePermissionProps>(function SharePermissi
/>
)}
<Box my={8}>
<CopyButton text={`${location.origin}/share?file=${encodeURIComponent(params)}`}>
<CopyButton text={getShareLink(bucketName, editDetail.objectName)}>
Copy Link
</CopyButton>
</Box>
Expand Down
14 changes: 9 additions & 5 deletions apps/dcellar-web-ui/src/modules/object/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { BatchOperations } from '@/modules/object/components/BatchOperations';

export const ObjectsPage = () => {
const dispatch = useAppDispatch();
const { allSps, primarySpInfo} = useAppSelector((root) => root.sp);
const { allSps, primarySpInfo } = useAppSelector((root) => root.sp);
const { bucketInfo } = useAppSelector((root) => root.bucket);
const { loginAccount } = useAppSelector((root) => root.persist);
const selectedRowKeys = useAppSelector((root) => root.object.selectedRowKeys);
Expand All @@ -45,11 +45,15 @@ export const ObjectsPage = () => {
if (!bucket) return;
const primarySp = primarySpInfo[bucketName];
if (!primarySp) {
const [data, error] = await getVirtualGroupFamily({ familyId: bucket.global_virtual_group_family_id });
const sp = allSps.find((item) => item.id === data?.globalVirtualGroupFamily?.primarySpId) as SpItem;
dispatch(setPrimarySpInfo({ bucketName, sp}));
const [data, error] = await getVirtualGroupFamily({
familyId: bucket.global_virtual_group_family_id,
});
const sp = allSps.find(
(item) => item.id === data?.globalVirtualGroupFamily?.primarySpId,
) as SpItem;
dispatch(setPrimarySpInfo({ bucketName, sp }));
}
}, [bucketInfo, bucketName])
}, [bucketInfo, bucketName]);

useAsyncEffect(async () => {
const bucket = bucketInfo[bucketName];
Expand Down
2 changes: 1 addition & 1 deletion apps/dcellar-web-ui/src/modules/share/SharedFile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { useAppDispatch, useAppSelector } from '@/store';
import { getSpOffChainData } from '@/store/slices/persist';
import { setupBucketQuota } from '@/store/slices/bucket';
import { useOffChainAuth } from '@/hooks/useOffChainAuth';
import { getSpUrlByBucketName, getVirtualGroupFamily } from '@/facade/virtual-group';
import { getSpUrlByBucketName } from '@/facade/virtual-group';
import { SpItem } from '@/store/slices/sp';
import { VisibilityType } from '../file/type';

Expand Down
Loading

0 comments on commit f4c1831

Please sign in to comment.