Skip to content

Commit ce96702

Browse files
committed
feat: add download task action button
1 parent 1c5de39 commit ce96702

File tree

9 files changed

+224
-72
lines changed

9 files changed

+224
-72
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "atom",
33
"productName": "AtomLauncher",
4-
"version": "0.2.0-beta.1",
4+
"version": "0.3.0",
55
"description": "My Electron application description",
66
"main": ".vite/build/main.js",
77
"scripts": {

src/main/services/client.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,17 +56,19 @@ export class ClientService {
5656
}
5757

5858
async install(event: IpcMainEvent, id: string, version: string) {
59-
const handler = createTaskHandler(event, 'install', 'Downloading Minecraft', id, version);
60-
const depsHandler = createTaskHandler(event, 'install', 'Installing Dependencies', id, version);
59+
const jsonTask = createTaskHandler(event, 'task:json', id, version);
60+
const jarTask = createTaskHandler(event, 'task:jar', id, version);
61+
const depsTask = createTaskHandler(event, 'task:dependencies', id, version);
6162

6263
const versionInfo = await this.resolveVersion(version);
6364
versionInfo.id = id;
6465

6566
await installVersionTask(
6667
this.minecraftFolder,
6768
versionInfo,
68-
handler,
69-
depsHandler
69+
jsonTask,
70+
jarTask,
71+
depsTask
7072
);
7173

7274
event.sender.send('on-complete');
@@ -75,14 +77,21 @@ export class ClientService {
7577
}
7678

7779
async installFabric(event: IpcMainEvent, id: string, version: string, loaderVersion: string) {
78-
const baseHandler = createTaskHandler(event, 'install', 'Downloading Fabric', id, version);
79-
const depsHandler = createTaskHandler(event, 'install', 'Installing Dependencies', id, version);
80+
const jsonTask = createTaskHandler(event, 'task:version', id, version);
81+
const jarTask = createTaskHandler(event, 'task:version', id, version);
82+
const depsTask = createTaskHandler(event, 'task:dependencies', id, version);
8083

8184
// 安装基础版本
8285
const inheritor = VersionUtils.createInheritVersionId(version);
8386
const mcInfo = await this.resolveVersion(version);
8487
mcInfo.id = inheritor;
85-
await installVersionTask(this.minecraftFolder, mcInfo, baseHandler, depsHandler);
88+
await installVersionTask(
89+
this.minecraftFolder,
90+
mcInfo,
91+
jsonTask,
92+
jarTask,
93+
depsTask
94+
);
8695

8796
// 安装Fabric
8897
const fabricVersionName = await installFabricByLoaderArtifact(
@@ -93,7 +102,7 @@ export class ClientService {
93102

94103
// 安装依赖
95104
await installDependenciesTask(await Version.parse(this.minecraftFolder, fabricVersionName))
96-
.startAndWait(depsHandler);
105+
.startAndWait(depsTask);
97106

98107
event.sender.send('on-complete');
99108

src/main/utils/client.util.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,23 @@ export type InstallTaskHandler = {
1616
// 安装任务通用处理器
1717
export const createTaskHandler = (
1818
event: IpcMainEvent,
19-
taskType: string,
20-
taskName: string,
19+
name: string,
2120
id: string,
2221
version: string
2322
): InstallTaskHandler => ({
2423
onUpdate: (task) => {
2524
event.sender.send('on-progress', {
2625
id,
2726
version,
28-
task: `task:${taskType}`,
29-
taskName,
27+
name,
3028
progress: (task.progress / task.total) * 100,
3129
});
3230
},
3331
onFailed: () => {
3432
event.sender.send('on-failed', {
3533
id,
3634
version,
37-
task: `task:${taskType}`,
35+
name,
3836
});
3937
},
4038
});
@@ -43,20 +41,21 @@ export const createTaskHandler = (
4341
export const installVersionTask = async (
4442
folder: MinecraftFolder,
4543
version: MinecraftVersion,
46-
handler: InstallTaskHandler,
47-
depsHandler: InstallTaskHandler,
44+
jsonTask: InstallTaskHandler,
45+
jarTask: InstallTaskHandler,
46+
depsTask: InstallTaskHandler,
4847
) => {
4948
// 安装JSON
5049
await new InstallJsonTask(version, folder, {})
51-
.startAndWait(handler);
50+
.startAndWait(jsonTask);
5251

5352
// 解析版本
5453
const parsedVersion = await Version.parse(folder, version.id);
5554
parsedVersion.id = version.id;
5655

5756
// 安装JAR
5857
await new InstallJarTask(parsedVersion, folder, {})
59-
.startAndWait(handler);
58+
.startAndWait(jarTask);
6059

6160
// 写入版本文件
6261
await writeFile(
@@ -66,7 +65,7 @@ export const installVersionTask = async (
6665

6766
// 安装依赖
6867
await installDependenciesTask(await Version.parse(folder, version.id))
69-
.startAndWait(depsHandler);
68+
.startAndWait(depsTask);
7069
};
7170

7271
// 获取启动配置

src/renderer/components/commons/pagination.tsx

Lines changed: 25 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
import clsx from 'clsx';
22
import React, { HTMLAttributes } from 'react';
3-
import { FaAngleDoubleLeft, FaAngleLeft, FaAngleRight, FaAngleDoubleRight } from 'react-icons/fa';
3+
import { FaAngleDoubleLeft, FaAngleLeft, FaAngleRight } from 'react-icons/fa';
44

55
type PaginationProps = {
66
currentPage: number;
7-
totalPages: number;
87
onPageChange: (page: number) => void;
98
};
109

1110
const Pagination: React.FC<HTMLAttributes<HTMLDivElement> & PaginationProps> = ({
1211
currentPage,
13-
totalPages,
1412
onPageChange,
1513
...props
1614
}) => {
@@ -19,7 +17,7 @@ const Pagination: React.FC<HTMLAttributes<HTMLDivElement> & PaginationProps> = (
1917
} = props;
2018
return (
2119
<div className={clsx('flex justify-center', className)}>
22-
<div className="w-[300px] bg-white dark:bg-neutral-800 rounded-xl shadow-md flex items-center justify-between transition-colors duration-300">
20+
<div className="bg-white dark:bg-neutral-800 rounded-xl shadow-md flex items-center justify-between transition-colors duration-300">
2321
{/* 首页按钮 */}
2422
<button
2523
className={`flex items-center justify-center h-10 w-10 rounded-lg transition-all duration-200 text-neutral-600 dark:text-neutral-300 hover:bg-primary/10 hover:text-primary dark:hover:bg-primary/20 ${currentPage === 1 ? 'opacity-50 cursor-not-allowed hover:bg-transparent hover:text-neutral-600 dark:hover:text-neutral-300' : ''}`}
@@ -29,39 +27,31 @@ const Pagination: React.FC<HTMLAttributes<HTMLDivElement> & PaginationProps> = (
2927
<FaAngleDoubleLeft />
3028
</button>
3129

32-
{/* 上一页按钮 */}
33-
<button
34-
className={`flex items-center justify-center h-10 w-10 rounded-lg transition-all duration-200 text-neutral-600 dark:text-neutral-300 hover:bg-primary/10 hover:text-primary dark:hover:bg-primary/20 ${currentPage === 1 ? 'opacity-50 cursor-not-allowed hover:bg-transparent hover:text-neutral-600 dark:hover:text-neutral-300' : ''}`}
35-
onClick={() => currentPage > 0 && onPageChange(currentPage - 1)}
36-
disabled={currentPage === 1}
37-
>
38-
<FaAngleLeft />
39-
</button>
40-
41-
{/* 页码显示 */}
42-
<div className="flex-1 flex justify-center">
43-
<span className="text-sm font-medium text-gray-900 dark:text-gray-50">
44-
Page <span> {currentPage} </span> of <span> {totalPages} </span>
45-
</span>
46-
</div>
30+
<div className="mr-5 w-24 flex justify-between items-center">
31+
{/* 上一页按钮 */}
32+
<button
33+
className={`flex items-center justify-center h-10 rounded-lg transition-all duration-200 text-neutral-600 dark:text-neutral-300 hover:bg-primary/10 hover:text-primary dark:hover:bg-primary/20 ${currentPage === 1 ? 'opacity-50 cursor-not-allowed hover:bg-transparent hover:text-neutral-600 dark:hover:text-neutral-300' : ''}`}
34+
onClick={() => currentPage > 0 && onPageChange(currentPage - 1)}
35+
disabled={currentPage === 1}
36+
>
37+
<FaAngleLeft />
38+
</button>
4739

48-
{/* 下一页按钮 */}
49-
<button
50-
className={`flex items-center justify-center h-10 w-10 rounded-lg transition-all duration-200 text-neutral-600 dark:text-neutral-300 hover:bg-primary/10 hover:text-primary dark:hover:bg-primary/20 ${!totalPages || currentPage === totalPages ? 'opacity-50 cursor-not-allowed hover:bg-transparent hover:text-neutral-600 dark:hover:text-neutral-300' : ''}`}
51-
onClick={() => currentPage > 0 && onPageChange(currentPage + 1)}
52-
disabled={!totalPages || currentPage === totalPages}
53-
>
54-
<FaAngleRight />
55-
</button>
40+
{/* 页码显示 */}
41+
<div className="w-10 flex justify-center">
42+
<span className="text-sm font-medium text-gray-900 dark:text-gray-50">
43+
<span> {currentPage} </span>
44+
</span>
45+
</div>
5646

57-
{/* 尾页按钮 */}
58-
<button
59-
className={`flex items-center justify-center h-10 w-10 rounded-lg transition-all duration-200 text-neutral-600 dark:text-neutral-300 hover:bg-primary/10 hover:text-primary dark:hover:bg-primary/20 ${!totalPages || currentPage === totalPages ? 'opacity-50 cursor-not-allowed hover:bg-transparent hover:text-neutral-600 dark:hover:text-neutral-300' : ''}`}
60-
onClick={() => currentPage > 0 && onPageChange(totalPages)}
61-
disabled={!totalPages || currentPage === totalPages}
62-
>
63-
<FaAngleDoubleRight />
64-
</button>
47+
{/* 下一页按钮 */}
48+
<button
49+
className="flex items-center justify-center h-10 rounded-lg transition-all duration-200 text-neutral-600 dark:text-neutral-300 hover:bg-primary/10 hover:text-primary dark:hover:bg-primary/20"
50+
onClick={() => currentPage > 0 && onPageChange(currentPage + 1)}
51+
>
52+
<FaAngleRight />
53+
</button>
54+
</div>
6555
</div>
6656
</div>
6757
);
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import { useTask } from '@renderer/hooks/store';
2+
import { useState } from 'react';
3+
import { FaDownload } from 'react-icons/fa';
4+
5+
export const TaskFloatingButton = () => {
6+
const { taskStates, isProcessing } = useTask();
7+
const [menuOpened, setMenuOpened] = useState(false);
8+
9+
const totalProgress = taskStates.reduce((sum, task) =>
10+
sum + task.progress, 0) / Math.max(1, taskStates.length);
11+
12+
if (!isProcessing || taskStates.length === 0) return null;
13+
14+
return (
15+
<>
16+
{menuOpened && <div className="fixed inset-0 z-10" onClick={() => setMenuOpened(false)}></div>}
17+
<div className="fixed bottom-6 right-6 z-20">
18+
{/* FAB 按钮 */}
19+
<button
20+
className="relative bg-blue-600 text-white rounded-full w-14 h-14 flex items-center justify-center shadow-lg hover:shadow-xl transition-shadow hover:bg-blue-600/90 animate-bounce-in cursor-pointer"
21+
onClick={() => setMenuOpened(prev => !prev)}
22+
>
23+
<FaDownload />
24+
{/* 进度环 */}
25+
<svg className="absolute inset-0 w-full h-full" viewBox="0 0 56 56">
26+
<circle
27+
cx={28}
28+
cy={28}
29+
r={24}
30+
fill="none"
31+
stroke="#165DFF20"
32+
strokeWidth={3}
33+
/>
34+
<circle
35+
id="progress-circle"
36+
className="progress-ring-circle"
37+
cx={28}
38+
cy={28}
39+
r={24}
40+
fill="none"
41+
stroke="#FFFFFF"
42+
strokeWidth={3}
43+
strokeDasharray="150.8"
44+
strokeDashoffset={`${150.8 * (1 - totalProgress / 100)}`}
45+
/>
46+
</svg>
47+
</button>
48+
{/* 任务菜单 (默认隐藏) */}
49+
<div
50+
style={{ display: menuOpened ? 'block' : 'none' }}
51+
className="bg-white rounded-lg shadow-xl absolute bottom-20 right-0 w-72 border border-gray-100 animate-fade-in"
52+
>
53+
<div className="p-4 border-b border-gray-100">
54+
<h3 className="font-semibold text-gray-800">后台任务</h3>
55+
</div>
56+
<div className="divide-y divide-gray-100">
57+
{/* 显示所有任务 */}
58+
{taskStates.filter(t => t.name === 'task:json').map(task => (
59+
<div key={task.name} className="p-4 hover:bg-gray-50 transition-colors">
60+
<div className="flex justify-between items-center mb-1">
61+
<span className="font-medium text-gray-800">Downloading version metadata</span>
62+
<span className={`text-xs font-medium ${task.progress === 100 ? 'text-green-500' : 'text-blue-500'}`}>
63+
{Number(task.progress.toFixed(0))}%
64+
</span>
65+
</div>
66+
<div className="w-full bg-gray-200 rounded-full h-1.5">
67+
<div
68+
className={`${task.progress === 100 ? 'bg-green-500' : 'bg-blue-500'} h-1.5 rounded-full transition-all duration-300`}
69+
style={{ width: `${task.progress}%` }}
70+
/>
71+
</div>
72+
{/* 任务完成状态图标 */}
73+
{Number(task.progress.toFixed(0)) === 100 && (
74+
<div className="mt-1 flex items-center text-green-500 text-xs">
75+
<i className="fa fa-check-circle mr-1" />
76+
<span>Done</span>
77+
</div>
78+
)}
79+
</div>
80+
))}
81+
{taskStates.filter(t => t.name === 'task:jar').map(task => (
82+
<div key={task.name} className="p-4 hover:bg-gray-50 transition-colors">
83+
<div className="flex justify-between items-center mb-1">
84+
<span className="font-medium text-gray-800">Downloading version jar</span>
85+
<span className={`text-xs font-medium ${task.progress === 100 ? 'text-green-500' : 'text-blue-500'}`}>
86+
{Number(task.progress.toFixed(0))}%
87+
</span>
88+
</div>
89+
<div className="w-full bg-gray-200 rounded-full h-1.5">
90+
<div
91+
className={`${task.progress === 100 ? 'bg-green-500' : 'bg-blue-500'} h-1.5 rounded-full transition-all duration-300`}
92+
style={{ width: `${task.progress}%` }}
93+
/>
94+
</div>
95+
{/* 任务完成状态图标 */}
96+
{Number(task.progress.toFixed(0)) === 100 && (
97+
<div className="mt-1 flex items-center text-green-500 text-xs">
98+
<i className="fa fa-check-circle mr-1" />
99+
<span>Done</span>
100+
</div>
101+
)}
102+
</div>
103+
))}
104+
{taskStates.filter(t => t.name === 'task:dependencies').map(task => (
105+
<div key={task.name} className="p-4 hover:bg-gray-50 transition-colors">
106+
<div className="flex justify-between items-center mb-1">
107+
<span className="font-medium text-gray-800">Downloading dependencies</span>
108+
<span className={`text-xs font-medium ${task.progress === 100 ? 'text-green-500' : 'text-blue-500'}`}>
109+
{Number(task.progress.toFixed(0))}%
110+
</span>
111+
</div>
112+
<div className="w-full bg-gray-200 rounded-full h-1.5">
113+
<div
114+
className={`${task.progress === 100 ? 'bg-green-500' : 'bg-blue-500'} h-1.5 rounded-full transition-all duration-300`}
115+
style={{ width: `${task.progress}%` }}
116+
/>
117+
</div>
118+
{/* 任务完成状态图标 */}
119+
{Number(task.progress.toFixed(0)) === 100 && (
120+
<div className="mt-1 flex items-center text-green-500 text-xs">
121+
<i className="fa fa-check-circle mr-1" />
122+
<span>Done</span>
123+
</div>
124+
)}
125+
</div>
126+
))}
127+
</div>
128+
<div
129+
className="p-4 border-t border-gray-100 flex justify-between items-center"
130+
onClick={() => null}
131+
>
132+
<span className="text-sm text-gray-500">查看全部任务</span>
133+
</div>
134+
</div>
135+
</div>
136+
</>
137+
);
138+
};

0 commit comments

Comments
 (0)