@bbki.ng/site 5.6.0 → 5.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/package.json +1 -1
- package/src/plugins/store/components/storePage.tsx +133 -60
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useCallback, useEffect } from 'react';
|
|
1
|
+
import React, { useCallback, useEffect, useState } from 'react';
|
|
2
2
|
import { Button, Table } from '@bbki.ng/ui';
|
|
3
3
|
|
|
4
4
|
import { IComPropsRegisteredToSlot } from '#/types/slots';
|
|
@@ -6,45 +6,160 @@ import { IPluginStoreEntry, PluginID } from '#/types/plugin';
|
|
|
6
6
|
|
|
7
7
|
import { StoreCtx } from '../context';
|
|
8
8
|
|
|
9
|
+
// 空状态展示
|
|
10
|
+
const EmptyState = () => (
|
|
11
|
+
<div className="flex flex-col items-center justify-center p-8 text-content-secondary">
|
|
12
|
+
<p className="text-sm">暂无可用插件</p>
|
|
13
|
+
</div>
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
// 错误状态展示
|
|
17
|
+
const ErrorState = ({ error, onRetry }: { error: Error; onRetry: () => void }) => (
|
|
18
|
+
<div className="flex flex-col items-center justify-center p-8">
|
|
19
|
+
<p className="text-content-danger text-sm mb-4">加载失败: {error.message}</p>
|
|
20
|
+
<Button onClick={onRetry} size="sm">
|
|
21
|
+
重试
|
|
22
|
+
</Button>
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
|
|
9
26
|
export const StorePage = (_: IComPropsRegisteredToSlot) => {
|
|
10
27
|
const { list, setLoading, isInstalled, install, uninstall } = StoreCtx.useCtx();
|
|
11
|
-
const [plugins, setPlugins] =
|
|
28
|
+
const [plugins, setPlugins] = useState<Array<IPluginStoreEntry>>([]);
|
|
29
|
+
const [error, setError] = useState<Error | null>(null);
|
|
12
30
|
|
|
13
31
|
useEffect(() => {
|
|
32
|
+
let cancelled = false;
|
|
33
|
+
|
|
14
34
|
const fetchPlugins = async () => {
|
|
15
35
|
setLoading(true);
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
36
|
+
setError(null);
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const pluginList = await list();
|
|
40
|
+
if (!cancelled) {
|
|
41
|
+
setPlugins(pluginList);
|
|
42
|
+
}
|
|
43
|
+
} catch (err) {
|
|
44
|
+
if (!cancelled) {
|
|
45
|
+
setError(err instanceof Error ? err : new Error('获取插件列表失败'));
|
|
46
|
+
}
|
|
47
|
+
} finally {
|
|
48
|
+
if (!cancelled) {
|
|
49
|
+
setLoading(false);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
19
52
|
};
|
|
20
53
|
|
|
21
54
|
fetchPlugins();
|
|
55
|
+
|
|
56
|
+
return () => {
|
|
57
|
+
cancelled = true;
|
|
58
|
+
};
|
|
22
59
|
}, [list, setLoading]);
|
|
23
60
|
|
|
61
|
+
const refreshList = useCallback(async () => {
|
|
62
|
+
try {
|
|
63
|
+
const pluginList = await list();
|
|
64
|
+
setPlugins(pluginList);
|
|
65
|
+
setError(null);
|
|
66
|
+
} catch (err) {
|
|
67
|
+
setError(err instanceof Error ? err : new Error('刷新列表失败'));
|
|
68
|
+
}
|
|
69
|
+
}, [list]);
|
|
70
|
+
|
|
24
71
|
const handleInstall = useCallback(
|
|
25
72
|
async (id: PluginID) => {
|
|
26
73
|
setLoading(true);
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
74
|
+
setError(null);
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
await install(id);
|
|
78
|
+
await refreshList();
|
|
79
|
+
} catch (err) {
|
|
80
|
+
setError(err instanceof Error ? err : new Error('安装失败'));
|
|
81
|
+
} finally {
|
|
82
|
+
setLoading(false);
|
|
83
|
+
}
|
|
31
84
|
},
|
|
32
|
-
[install,
|
|
85
|
+
[install, refreshList, setLoading]
|
|
33
86
|
);
|
|
34
87
|
|
|
35
88
|
const handleUninstall = useCallback(
|
|
36
89
|
async (id: PluginID) => {
|
|
37
90
|
setLoading(true);
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
91
|
+
setError(null);
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
await uninstall(id);
|
|
95
|
+
await refreshList();
|
|
96
|
+
} catch (err) {
|
|
97
|
+
setError(err instanceof Error ? err : new Error('卸载失败'));
|
|
98
|
+
} finally {
|
|
99
|
+
setLoading(false);
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
[uninstall, refreshList, setLoading]
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const headerRenderer = useCallback(() => {
|
|
106
|
+
return (
|
|
107
|
+
<>
|
|
108
|
+
<Table.HCell>插件名称</Table.HCell>
|
|
109
|
+
<Table.HCell>插件描述</Table.HCell>
|
|
110
|
+
<Table.HCell style={{ textAlign: 'right' }}>安装/卸载</Table.HCell>
|
|
111
|
+
</>
|
|
112
|
+
);
|
|
113
|
+
}, []);
|
|
114
|
+
|
|
115
|
+
const rowRenderer = useCallback(
|
|
116
|
+
(index: number) => {
|
|
117
|
+
if (index < 0 || index >= plugins.length) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const plugin = plugins[index];
|
|
122
|
+
const pluginInstalled = isInstalled(plugin.id);
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<>
|
|
126
|
+
<Table.Cell>{plugin.name}</Table.Cell>
|
|
127
|
+
<Table.Cell>{plugin.description}</Table.Cell>
|
|
128
|
+
<Table.Cell style={{ textAlign: 'right' }}>
|
|
129
|
+
{pluginInstalled ? (
|
|
130
|
+
<Button
|
|
131
|
+
variant="ghost"
|
|
132
|
+
className="text-content-danger"
|
|
133
|
+
onClick={() => handleUninstall(plugin.id)}
|
|
134
|
+
>
|
|
135
|
+
卸载
|
|
136
|
+
</Button>
|
|
137
|
+
) : (
|
|
138
|
+
<Button color="primary" onClick={() => handleInstall(plugin.id)}>
|
|
139
|
+
安装
|
|
140
|
+
</Button>
|
|
141
|
+
)}
|
|
142
|
+
</Table.Cell>
|
|
143
|
+
</>
|
|
144
|
+
);
|
|
42
145
|
},
|
|
43
|
-
[
|
|
146
|
+
[plugins, isInstalled, handleInstall, handleUninstall]
|
|
44
147
|
);
|
|
45
148
|
|
|
149
|
+
if (error) {
|
|
150
|
+
return (
|
|
151
|
+
<div className="prose">
|
|
152
|
+
<ErrorState error={error} onRetry={refreshList} />
|
|
153
|
+
</div>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
46
157
|
if (plugins.length === 0) {
|
|
47
|
-
return
|
|
158
|
+
return (
|
|
159
|
+
<div className="prose">
|
|
160
|
+
<EmptyState />
|
|
161
|
+
</div>
|
|
162
|
+
);
|
|
48
163
|
}
|
|
49
164
|
|
|
50
165
|
return (
|
|
@@ -52,50 +167,8 @@ export const StorePage = (_: IComPropsRegisteredToSlot) => {
|
|
|
52
167
|
<Table
|
|
53
168
|
className="w-full"
|
|
54
169
|
rowCount={plugins.length}
|
|
55
|
-
headerRenderer={
|
|
56
|
-
|
|
57
|
-
<>
|
|
58
|
-
<Table.HCell>插件名称</Table.HCell>
|
|
59
|
-
<Table.HCell>插件描述</Table.HCell>
|
|
60
|
-
<Table.HCell style={{ textAlign: 'right' }}>安装/卸载</Table.HCell>
|
|
61
|
-
</>
|
|
62
|
-
);
|
|
63
|
-
}}
|
|
64
|
-
rowRenderer={index => {
|
|
65
|
-
const isIdxValid = index >= 0 && index < plugins.length;
|
|
66
|
-
if (!isIdxValid) {
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
const plugin = plugins[index];
|
|
70
|
-
return (
|
|
71
|
-
<>
|
|
72
|
-
<Table.Cell>{plugin.name}</Table.Cell>
|
|
73
|
-
<Table.Cell>{plugin.description}</Table.Cell>
|
|
74
|
-
<Table.Cell style={{ textAlign: 'right' }}>
|
|
75
|
-
{isInstalled(plugin.id) ? (
|
|
76
|
-
<Button
|
|
77
|
-
variant="ghost"
|
|
78
|
-
className="text-content-danger"
|
|
79
|
-
onClick={() => {
|
|
80
|
-
handleUninstall(plugin.id);
|
|
81
|
-
}}
|
|
82
|
-
>
|
|
83
|
-
卸载
|
|
84
|
-
</Button>
|
|
85
|
-
) : (
|
|
86
|
-
<Button
|
|
87
|
-
color="primary"
|
|
88
|
-
onClick={() => {
|
|
89
|
-
handleInstall(plugin.id);
|
|
90
|
-
}}
|
|
91
|
-
>
|
|
92
|
-
安装
|
|
93
|
-
</Button>
|
|
94
|
-
)}
|
|
95
|
-
</Table.Cell>
|
|
96
|
-
</>
|
|
97
|
-
);
|
|
98
|
-
}}
|
|
170
|
+
headerRenderer={headerRenderer}
|
|
171
|
+
rowRenderer={rowRenderer}
|
|
99
172
|
/>
|
|
100
173
|
</div>
|
|
101
174
|
);
|