From af91da14003c2be799ae62eed9be1bb09ad00008 Mon Sep 17 00:00:00 2001
From: devinxl <94832688+devinxl@users.noreply.github.com>
Date: Thu, 16 May 2024 17:02:01 +0800
Subject: [PATCH] feat(dcellar-web-ui): introduce the stop upload feature
(#385)
* feat(dcellar-web-ui): introduce the stop upload feature
* fix(dcellar-web-ui): the uploading name text ellispsis
* fix(dcellar-web-ui): change the stop status icon
* feat(dcellar-web-ui): introduce activities feature for bucket, object and group
* fix(dcellar-web-ui): text case error
* refactor(dcellar-web-ui): the transfer in style & toolbox style
* feat(dcellar-web-ui): add discord and release note link
* feat(dcellar-web-ui): introduce the stop upload feature
---
.../public/js/iconfont_v0.1.12.min.js | 1 +
.../GlobalObjectUploadManager.tsx | 26 ++++++-
.../src/modules/upload/ObjectUploadStatus.tsx | 14 ++--
.../src/modules/upload/UploadActionButton.tsx | 65 +++++++++++++----
.../src/modules/upload/UploadingObjects.tsx | 15 ++--
.../modules/upload/UploadingObjectsList.tsx | 43 +++++++-----
.../modules/upload/useTaskManagementTab.tsx | 22 +++---
apps/dcellar-web-ui/src/pages/_document.tsx | 2 +-
.../dcellar-web-ui/src/store/slices/global.ts | 69 +++++++++++++++----
9 files changed, 193 insertions(+), 64 deletions(-)
create mode 100644 apps/dcellar-web-ui/public/js/iconfont_v0.1.12.min.js
diff --git a/apps/dcellar-web-ui/public/js/iconfont_v0.1.12.min.js b/apps/dcellar-web-ui/public/js/iconfont_v0.1.12.min.js
new file mode 100644
index 00000000..1e4a3576
--- /dev/null
+++ b/apps/dcellar-web-ui/public/js/iconfont_v0.1.12.min.js
@@ -0,0 +1 @@
+!function(e){var t,n,d,o,i,a,r='';function c(){i||(i=!0,d())}t=function(){var e,t,n;(n=document.createElement("div")).innerHTML=r,r=null,(t=n.getElementsByTagName("svg")[0])&&(t.setAttribute("aria-hidden","true"),t.style.position="absolute",t.style.width=0,t.style.height=0,t.style.overflow="hidden",e=t,(n=document.body).firstChild?(t=n.firstChild).parentNode.insertBefore(e,t):n.appendChild(e))},document.addEventListener?["complete","loaded","interactive"].indexOf(document.readyState)>-1?setTimeout(t,0):(n=function(){document.removeEventListener("DOMContentLoaded",n,!1),t()},document.addEventListener("DOMContentLoaded",n,!1)):document.attachEvent&&(d=t,o=e.document,i=!1,(a=function(){try{o.documentElement.doScroll("left")}catch(e){return void setTimeout(a,50)}c()})(),o.onreadystatechange=function(){"complete"==o.readyState&&(o.onreadystatechange=null,c())})}(window);
\ No newline at end of file
diff --git a/apps/dcellar-web-ui/src/components/layout/GlobalManagements/GlobalObjectUploadManager.tsx b/apps/dcellar-web-ui/src/components/layout/GlobalManagements/GlobalObjectUploadManager.tsx
index d776f4fb..a94a817a 100644
--- a/apps/dcellar-web-ui/src/components/layout/GlobalManagements/GlobalObjectUploadManager.tsx
+++ b/apps/dcellar-web-ui/src/components/layout/GlobalManagements/GlobalObjectUploadManager.tsx
@@ -222,6 +222,7 @@ export const GlobalObjectUploadManager = memo(
} else {
axios
.put(url, task.waitObject.file, {
+ signal: task.abortController?.signal,
async onUploadProgress(progressEvent) {
const progress = progressEvent.total
? Math.floor((progressEvent.loaded / progressEvent.total) * 100)
@@ -265,6 +266,7 @@ export const GlobalObjectUploadManager = memo(
setupUploadTaskErrorMsg({
account: loginAccount,
task,
+ status: e?.code === 'ERR_CANCELED' ? 'CANCEL' : 'ERROR',
errorMsg: authExpired
? 'Authentication expired.'
: message || e?.message || 'upload error',
@@ -461,8 +463,28 @@ export const GlobalObjectUploadManager = memo(
// 3. upload
useAsyncEffect(async () => {
if (!uploadTasks.length) return;
- dispatch(updateUploadStatus({ ids: uploadTasks, status: 'UPLOAD', account: loginAccount }));
- const tasks = queue.filter((t) => uploadTasks.includes(t.id));
+ // Add abortController to each task
+ const extraFields: Record> = uploadTasks.reduce(
+ (acc, id) => {
+ acc[id] = {
+ abortController: new AbortController(),
+ };
+ return acc;
+ },
+ {} as Record>,
+ );
+ dispatch(
+ updateUploadStatus({
+ ids: uploadTasks,
+ status: 'UPLOAD',
+ account: loginAccount,
+ extraFields,
+ }),
+ );
+
+ const tasks = queue
+ .filter((t) => uploadTasks.includes(t.id))
+ .map((t) => ({ ...t, ...extraFields[t.id] }));
tasks.forEach(runUploadTask);
}, [uploadTasks.join('')]);
diff --git a/apps/dcellar-web-ui/src/modules/upload/ObjectUploadStatus.tsx b/apps/dcellar-web-ui/src/modules/upload/ObjectUploadStatus.tsx
index 04d298e3..e0ab9a25 100644
--- a/apps/dcellar-web-ui/src/modules/upload/ObjectUploadStatus.tsx
+++ b/apps/dcellar-web-ui/src/modules/upload/ObjectUploadStatus.tsx
@@ -1,10 +1,14 @@
import { UploadObject } from '@/store/slices/global';
-import { Text } from '@node-real/uikit';
import { Loading } from '@/components/common/Loading';
import { UploadProgress } from './UploadProgress';
import { IconFont } from '@/components/IconFont';
+import { memo } from 'react';
-export const ObjectUploadStatus = ({ task }: { task: UploadObject }) => {
+export const ObjectUploadStatus = memo(function ObjectUploadStatus({
+ task,
+}: {
+ task: UploadObject;
+}) {
switch (task.status) {
case 'RETRY_CHECK':
case 'RETRY_CHECKING':
@@ -61,11 +65,11 @@ export const ObjectUploadStatus = ({ task }: { task: UploadObject }) => {
case 'CANCEL':
return (
<>
-
- Cancelled
+
+ Stopped
>
);
default:
return null;
}
-};
+});
diff --git a/apps/dcellar-web-ui/src/modules/upload/UploadActionButton.tsx b/apps/dcellar-web-ui/src/modules/upload/UploadActionButton.tsx
index 9721f0bc..03f23425 100644
--- a/apps/dcellar-web-ui/src/modules/upload/UploadActionButton.tsx
+++ b/apps/dcellar-web-ui/src/modules/upload/UploadActionButton.tsx
@@ -1,36 +1,58 @@
import { IconFont } from '@/components/IconFont';
import { DCButton } from '@/components/common/DCButton';
import { useAppDispatch, useAppSelector } from '@/store';
-import { clearUploadRecords, retryUploadTasks } from '@/store/slices/global';
+import {
+ cancelUploadingRequests,
+ clearUploadRecords,
+ retryUploadTasks,
+ updateUploadStatus,
+} from '@/store/slices/global';
+<<<<<<< HEAD
+<<<<<<< HEAD
+import React, { useCallback } from 'react';
+=======
+=======
+>>>>>>> f143b09b (feat(dcellar-web-ui): introduce the stop upload feature)
import React from 'react';
+>>>>>>> 2ee2f675 (feat(dcellar-web-ui): introduce the stop upload feature)
export type ActionButtonProps = {
- type: 'clear' | 'retry' | 'clear-all' | 'retry-all';
+ type: 'clear' | 'retry' | 'clear-all' | 'retry-all' | 'cancel' | 'cancel-all';
ids: number[];
text?: string;
};
const actionItems = [
{
- type: 'clear',
- text: 'Clear',
- icon: 'delete',
+ type: 'cancel',
+ text: 'Cancel',
+ icon: 'stop',
+ },
+ {
+ type: 'cancel-all',
+ text: 'Stop Uploading',
+ icon: 'stop',
},
{
type: 'retry',
text: 'Retry',
icon: 'retry',
},
- {
- type: 'clear-all',
- text: 'Clear All Records',
- icon: 'delete',
- },
{
type: 'retry-all',
text: 'Retry All',
icon: 'retry',
},
+ {
+ type: 'clear',
+ text: 'Clear',
+ icon: 'delete',
+ },
+ {
+ type: 'clear-all',
+ text: 'Clear All Records',
+ icon: 'delete',
+ },
];
export const UploadActionButton = React.memo(function UploadActionButton({
@@ -42,6 +64,19 @@ export const UploadActionButton = React.memo(function UploadActionButton({
const dispatch = useAppDispatch();
const actionItem = actionItems.find((item) => item.type === type);
+ const onCancel = useCallback(
+ (ids: number[]) => {
+ dispatch(
+ updateUploadStatus({
+ account: loginAccount,
+ ids,
+ status: 'CANCEL',
+ }),
+ );
+ dispatch(cancelUploadingRequests({ ids }));
+ },
+ [dispatch, loginAccount],
+ );
const onClear = (ids: number[]) => {
dispatch(clearUploadRecords({ ids, loginAccount }));
};
@@ -50,14 +85,18 @@ export const UploadActionButton = React.memo(function UploadActionButton({
};
const onClick = () => {
switch (type) {
- case 'clear':
- case 'clear-all':
- onClear(ids);
+ case 'cancel':
+ case 'cancel-all':
+ onCancel(ids);
break;
case 'retry':
case 'retry-all':
onRetry(ids);
break;
+ case 'clear':
+ case 'clear-all':
+ onClear(ids);
+ break;
default:
break;
}
diff --git a/apps/dcellar-web-ui/src/modules/upload/UploadingObjects.tsx b/apps/dcellar-web-ui/src/modules/upload/UploadingObjects.tsx
index 4b9cd11d..d1c33a5e 100644
--- a/apps/dcellar-web-ui/src/modules/upload/UploadingObjects.tsx
+++ b/apps/dcellar-web-ui/src/modules/upload/UploadingObjects.tsx
@@ -19,7 +19,7 @@ import { UploadingPanelKey, useTaskManagementTab } from './useTaskManagementTab'
import { IconFont } from '@/components/IconFont';
import { UploadingObjectsList } from './UploadingObjectsList';
-import { UploadObject } from '@/store/slices/global';
+import { UPLOADING_STATUSES, UPLOAD_FAILED_STATUSES, UploadObject } from '@/store/slices/global';
import { UploadActionButton } from './UploadActionButton';
interface UploadingObjectsProps {}
@@ -36,15 +36,20 @@ export const UploadingObjects = memo(function UploadingOb
panelKey: UploadingPanelKey;
data: UploadObject[];
}) => {
- if ([UploadingPanelKey.ALL, UploadingPanelKey.UPLOADING].includes(panelKey)) {
- return null;
- }
return (
{panelKey === UploadingPanelKey.COMPLETE && (
item.id)} />
)}
- {panelKey === UploadingPanelKey.FAILED && (
+ {(panelKey === UploadingPanelKey.UPLOADING || panelKey === UploadingPanelKey.ALL) && (
+ UPLOADING_STATUSES.includes(item.status))
+ .map((item) => item.id)}
+ />
+ )}
+ {(panelKey === UploadingPanelKey.FAILED || panelKey === UploadingPanelKey.STOPPED) && (
item.id)} />
item.id)} />
diff --git a/apps/dcellar-web-ui/src/modules/upload/UploadingObjectsList.tsx b/apps/dcellar-web-ui/src/modules/upload/UploadingObjectsList.tsx
index 191cf48a..22060ca0 100644
--- a/apps/dcellar-web-ui/src/modules/upload/UploadingObjectsList.tsx
+++ b/apps/dcellar-web-ui/src/modules/upload/UploadingObjectsList.tsx
@@ -1,10 +1,5 @@
import { DCTable } from '@/components/common/DCTable';
-import {
- UPLOADING_STATUSES,
- UPLOAD_FAILED_STATUSES,
- UPLOAD_SUCCESS_STATUS,
- UploadObject,
-} from '@/store/slices/global';
+import { UploadObject } from '@/store/slices/global';
import { ColumnProps } from 'antd/es/table';
import React, { useState } from 'react';
import { NameItem } from './NameItem';
@@ -34,7 +29,6 @@ export const UploadingObjectsList = ({ data }: { data: UploadObject[] }) => {
size={record.waitObject.size}
msg={record.msg}
status={record.status}
- w={234}
task={record}
/>
);
@@ -72,7 +66,8 @@ export const UploadingObjectsList = ({ data }: { data: UploadObject[] }) => {
title: 'Action',
width: 146,
render: (record) => {
- if (UPLOADING_STATUSES.includes(record.status)) {
+ const { status, id } = record;
+ if (['SEAL', 'SEALING'].includes(status)) {
return (
--
@@ -80,15 +75,29 @@ export const UploadingObjectsList = ({ data }: { data: UploadObject[] }) => {
);
}
- if (UPLOAD_SUCCESS_STATUS === record.status) {
- return ;
- } else if (UPLOAD_FAILED_STATUSES.includes(record.status)) {
- return (
-
-
-
-
- );
+ switch (status) {
+ case 'FINISH':
+ return ;
+
+ case 'CANCEL':
+ case 'ERROR':
+ return (
+
+
+
+
+ );
+
+ case 'WAIT':
+ case 'HASH':
+ case 'HASHED':
+ case 'SIGN':
+ case 'SIGNED':
+ case 'UPLOAD':
+ return ;
+
+ default:
+ return null;
}
},
},
diff --git a/apps/dcellar-web-ui/src/modules/upload/useTaskManagementTab.tsx b/apps/dcellar-web-ui/src/modules/upload/useTaskManagementTab.tsx
index 19bcb443..1d63ff10 100644
--- a/apps/dcellar-web-ui/src/modules/upload/useTaskManagementTab.tsx
+++ b/apps/dcellar-web-ui/src/modules/upload/useTaskManagementTab.tsx
@@ -1,14 +1,15 @@
import { useAppSelector } from '@/store';
-import { UploadObject } from '@/store/slices/global';
+import { UPLOADING_STATUSES, UploadObject } from '@/store/slices/global';
import { sortBy } from 'lodash-es';
import { useMemo, useState } from 'react';
export enum UploadingPanelKey {
ALL = 'ALL',
- UPLOADING = 'HASH-UPLOAD-SEAL',
+ UPLOADING = 'RETRY-WAIT-HASH-UPLOAD-SIGN-SEAL',
+ STOPPED = 'CANCEL',
COMPLETE = 'FINISH',
- FAILED = 'ERROR-CANCEL',
+ FAILED = 'ERROR',
}
export const useTaskManagementTab = () => {
@@ -17,14 +18,14 @@ export const useTaskManagementTab = () => {
const queue = sortBy(objectUploadQueue[loginAccount] || [], (o) => o.waitObject.time);
- const { uploadingQueue, completeQueue, errorQueue } = useMemo(() => {
- const uploadingQueue = queue?.filter((i) =>
- ['HASH', 'UPLOAD', 'SEAL', 'SEALING'].includes(i.status),
- );
+ const { uploadingQueue, stoppedQueue, completeQueue, errorQueue } = useMemo(() => {
+ const uploadingQueue = queue?.filter((i) => UPLOADING_STATUSES.includes(i.status));
const completeQueue = queue?.filter((i) => i.status === 'FINISH');
- const errorQueue = queue?.filter((i) => ['ERROR', 'CANCEL'].includes(i.status));
+ const stoppedQueue = queue?.filter((i) => i.status === 'CANCEL');
+ const errorQueue = queue?.filter((i) => ['ERROR'].includes(i.status));
return {
uploadingQueue,
+ stoppedQueue,
completeQueue,
errorQueue,
};
@@ -46,6 +47,11 @@ export const useTaskManagementTab = () => {
key: UploadingPanelKey.UPLOADING,
data: uploadingQueue,
},
+ {
+ title: 'Stopped',
+ key: UploadingPanelKey.STOPPED,
+ data: stoppedQueue,
+ },
{
title: 'Complete',
key: UploadingPanelKey.COMPLETE,
diff --git a/apps/dcellar-web-ui/src/pages/_document.tsx b/apps/dcellar-web-ui/src/pages/_document.tsx
index c150f269..e5bed808 100644
--- a/apps/dcellar-web-ui/src/pages/_document.tsx
+++ b/apps/dcellar-web-ui/src/pages/_document.tsx
@@ -26,7 +26,7 @@ export default function Document() {
__html: `window.__ASSET_PREFIX = ${flatted.stringify(assetPrefix)}`,
}}
>
-
+
diff --git a/apps/dcellar-web-ui/src/store/slices/global.ts b/apps/dcellar-web-ui/src/store/slices/global.ts
index 697906cc..2526221a 100644
--- a/apps/dcellar-web-ui/src/store/slices/global.ts
+++ b/apps/dcellar-web-ui/src/store/slices/global.ts
@@ -105,6 +105,7 @@ export type UploadObject = {
msg: string;
progress: number;
delegateUpload?: boolean;
+ abortController?: AbortController;
};
export interface GlobalState {
@@ -175,14 +176,32 @@ export const globalSlice = createSlice({
state,
{
payload,
- }: PayloadAction<{ account: string; ids: number[]; status: UploadObject['status'] }>,
+ }: PayloadAction<{
+ account: string;
+ ids: number[];
+ status: UploadObject['status'];
+ extraFields?: Record>;
+ }>,
) {
- const { account, ids, status } = payload;
+ const { account, ids, status, extraFields } = payload;
const isErrorStatus = UPLOAD_FAILED_STATUSES.includes(status);
const queue = state.objectUploadQueue[account] || [];
- state.objectUploadQueue[account] = queue.map((q) =>
- ids.includes(q.id) ? { ...q, status, msg: isErrorStatus ? q.msg : '' } : q,
- );
+ state.objectUploadQueue[account] = queue.map((item) => {
+ if (ids.includes(item.id)) {
+ const updatedItem = { ...item, ...extraFields?.[item.id], status };
+
+ if (status === 'RETRY_CHECK') {
+ return { ...updatedItem, msg: '', progress: 0 };
+ }
+ if (isErrorStatus) {
+ return { ...updatedItem, msg: item.msg };
+ }
+
+ return updatedItem;
+ }
+
+ return item;
+ });
if (status === 'SEAL') {
ids.forEach((id) => {
@@ -222,15 +241,17 @@ export const globalSlice = createSlice({
task.status = 'ERROR';
task.msg = msg;
},
- updateUploadTaskMsg(
+ updateUploadTaskErrorMsg(
state,
- { payload }: PayloadAction<{ account: string; id: number; msg: string }>,
+ {
+ payload,
+ }: PayloadAction<{ account: string; id: number; msg: string; status?: UploadObjectStatus }>,
) {
- const { id, msg } = payload;
+ const { id, msg, status } = payload;
const task = find(state.objectUploadQueue[payload.account], (t) => t.id === id);
if (!task) return;
- task.status = 'ERROR';
- task.msg = msg;
+ task.status = task.status !== 'CANCEL' ? status ?? 'ERROR' : 'CANCEL';
+ task.msg = task.status !== 'CANCEL' ? msg : '';
},
updateWaitObjectStatus(
state,
@@ -429,7 +450,7 @@ export const {
setBnbUsdtExchangeRate,
updateWaitObjectStatus,
updateWaitTaskMsg,
- updateUploadTaskMsg,
+ updateUploadTaskErrorMsg,
updateUploadChecksum,
addToUploadQueue,
updateUploadStatus,
@@ -697,14 +718,25 @@ export const addDelegatedTasksToUploadQueue =
};
export const setupUploadTaskErrorMsg =
- ({ account, task, errorMsg }: { account: string; task: UploadObject; errorMsg: string }) =>
+ ({
+ account,
+ task,
+ errorMsg,
+ status,
+ }: {
+ account: string;
+ task: UploadObject;
+ errorMsg: string;
+ status?: UploadObjectStatus;
+ }) =>
async (dispatch: AppDispatch) => {
// const isFolder = task.waitObject.name.endsWith('/');
dispatch(
- updateUploadTaskMsg({
+ updateUploadTaskErrorMsg({
account,
id: task.id,
msg: errorMsg || 'The object failed to be created.',
+ status,
}),
);
// isFolder && dispatch(cancelUploadFolder({ account, folderName: task.waitObject.name }));
@@ -738,4 +770,15 @@ export const retryUploadTasks =
});
};
+export const cancelUploadingRequests =
+ ({ ids }: { ids: number[] }) =>
+ async (dispatch: AppDispatch, getState: GetState) => {
+ const { loginAccount } = getState().persist;
+ const uploadQueue = getState().global.objectUploadQueue[loginAccount] || _emptyUploadQueue;
+ const tasks = uploadQueue.filter((task) => ids.includes(task.id));
+ tasks.forEach(async (task) => {
+ task.abortController && task.abortController.abort();
+ });
+ };
+
export default globalSlice.reducer;