@nextclaw/ui 0.24.0-beta.0 → 0.24.0-beta.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 +17 -0
- package/dist/assets/api-BqVIq0lF.js +13 -0
- package/dist/assets/{appearance-settings-page-CPoFZLw8.js → appearance-settings-page-CTWn1eAT.js} +1 -1
- package/dist/assets/{book-open-jcUt0rr9.js → book-open-o2x7OLiO.js} +1 -1
- package/dist/assets/{channels-list-page-anMJAkfK.js → channels-list-page-Bzn-pzyJ.js} +1 -1
- package/dist/assets/{config-split-page-AcuFnbIC.js → config-split-page-CnRbXXlh.js} +1 -1
- package/dist/assets/{confirm-dialog-B-atCfC5.js → confirm-dialog-B_Hbcra2.js} +1 -1
- package/dist/assets/{createLucideIcon-Blz0FzrV.js → createLucideIcon-rwwpqyks.js} +1 -1
- package/dist/assets/{desktop-capabilities-page-8lKF_Ob-.js → desktop-capabilities-page-C2ayYQTr.js} +1 -1
- package/dist/assets/{desktop-update-config-DABXDpbj.js → desktop-update-config-C13czpyL.js} +1 -1
- package/dist/assets/{dist-eH-_j8yU.js → dist-BsF3hfOi.js} +1 -1
- package/dist/assets/{dist-D8kcEpTf.js → dist-aNN3iQxV.js} +1 -1
- package/dist/assets/{doc-browser-DzXRehlW.js → doc-browser-D8dIOo9E.js} +1 -1
- package/dist/assets/doc-browser-DcuOOtpP.js +1 -0
- package/dist/assets/{doc-browser-context-BlbUcg2J.js → doc-browser-context-Dhsit2Ma.js} +1 -1
- package/dist/assets/{extensions-mquwAJ2Z.js → extensions-CD6xvKF_.js} +1 -1
- package/dist/assets/{form-actions-CaZDGEmB.js → form-actions-CC4Wz_lL.js} +1 -1
- package/dist/assets/index-BTsbdtv6.js +109 -0
- package/dist/assets/index-Bcfy2kTh.css +1 -0
- package/dist/assets/loader-circle-B6aFbB-8.js +1 -0
- package/dist/assets/mcp-marketplace-page-BtUAypA7.js +1 -0
- package/dist/assets/{mcp-marketplace-page-gFtgyueL.js → mcp-marketplace-page-r2MmD9xO.js} +1 -1
- package/dist/assets/{model-config-page-hFBpeRAO.js → model-config-page-DboxoT-I.js} +1 -1
- package/dist/assets/{navigation-link-Hb5Z0UxZ.js → navigation-link-C_6oj3zz.js} +1 -1
- package/dist/assets/plus-DRsq9Kvs.js +1 -0
- package/dist/assets/{privacy-settings-page-C18zo3ox.js → privacy-settings-page-C2sDBqhb.js} +1 -1
- package/dist/assets/projects-BcPcx-uD.js +1 -0
- package/dist/assets/{providers-config-page-YU0-AOXJ.js → providers-config-page-CmUFl9RF.js} +1 -1
- package/dist/assets/react-BjO9I4WZ.js +8 -0
- package/dist/assets/{refresh-cw-ZHKfE3yx.js → refresh-cw-DsT2kvXi.js} +1 -1
- package/dist/assets/remote-Ci6x91ES.js +1 -0
- package/dist/assets/{rotate-cw-CLhOXGwE.js → rotate-cw-BCAs6c3R.js} +1 -1
- package/dist/assets/{runtime-config-page-Dmy1A_0Y.js → runtime-config-page-cQKNeAjL.js} +1 -1
- package/dist/assets/{save-CWuuZs77.js → save-CadRMTMQ.js} +1 -1
- package/dist/assets/{search-DcmsKAn2.js → search-B6tB4fzB.js} +1 -1
- package/dist/assets/{search-config-page-CxBacx6W.js → search-config-page-C8lOIkxn.js} +1 -1
- package/dist/assets/{secrets-config-page-B4py8-KR.js → secrets-config-page-8clzAag2.js} +1 -1
- package/dist/assets/security-config-DPbM6IVT.js +1 -0
- package/dist/assets/{select-BEr0Iy_f.js → select-CO9S_C2C.js} +1 -1
- package/dist/assets/skeleton-IHILNhIW.js +1 -0
- package/dist/assets/{tag-chip-ByqDkn6g.js → tag-chip-BZKk0UkK.js} +1 -1
- package/dist/assets/x-BuvHIb1d.js +1 -0
- package/dist/index.html +21 -21
- package/package.json +5 -5
- package/src/features/apps/components/__tests__/app-packages-panel.test.tsx +362 -241
- package/src/features/apps/components/app-document-access-section.tsx +187 -0
- package/src/features/apps/components/app-package-card.tsx +150 -275
- package/src/features/apps/hooks/use-app-packages.ts +91 -35
- package/src/features/chat/index.ts +5 -1
- package/src/features/projects/AGENTS.md +3 -2
- package/src/features/projects/components/project-overview.tsx +70 -139
- package/src/features/projects/components/work/project-work-item-drawer.test.tsx +65 -0
- package/src/features/projects/components/work/project-work-item-drawer.tsx +367 -0
- package/src/features/projects/components/work/project-work-items.test.tsx +98 -0
- package/src/features/projects/components/work/project-work-items.tsx +199 -0
- package/src/features/projects/components/work/project-work-state-settings.tsx +285 -0
- package/src/features/projects/hooks/use-project-observation.ts +56 -26
- package/src/features/projects/hooks/use-project-work.test.tsx +58 -0
- package/src/features/projects/hooks/use-project-work.ts +215 -0
- package/src/features/projects/pages/project-home-page.tsx +119 -138
- package/src/features/projects/pages/projects-page.test.tsx +79 -308
- package/src/shared/lib/i18n/locales/en-US/doc-browser.json +16 -1
- package/src/shared/lib/i18n/locales/en-US/projects.json +60 -1
- package/src/shared/lib/i18n/locales/zh-CN/doc-browser.json +16 -1
- package/src/shared/lib/i18n/locales/zh-CN/projects.json +60 -1
- package/dist/assets/api-CoqPRKmF.js +0 -13
- package/dist/assets/doc-browser-DJEMr7Zk.js +0 -1
- package/dist/assets/index-DyR5iR0O.css +0 -1
- package/dist/assets/index-XxXj8WDS.js +0 -109
- package/dist/assets/loader-circle-0qPOOYQ5.js +0 -1
- package/dist/assets/mcp-marketplace-page-uanamJ6L.js +0 -1
- package/dist/assets/plus-CYOvNvcV.js +0 -1
- package/dist/assets/projects-C8Gm9RS8.js +0 -1
- package/dist/assets/react-DF-kP7iN.js +0 -8
- package/dist/assets/remote-B_utkBra.js +0 -1
- package/dist/assets/security-config-CTP4IQWw.js +0 -1
- package/dist/assets/skeleton-B2NHluWf.js +0 -1
- package/dist/assets/x-CENkFyK5.js +0 -1
- package/src/features/projects/components/project-work-items.test.tsx +0 -78
- package/src/features/projects/components/project-work-items.tsx +0 -272
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
import { useRef, useState } from
|
|
2
|
-
import type {
|
|
3
|
-
AppDataEntry,
|
|
4
|
-
AppPackageOperationView,
|
|
5
|
-
AppPackageView,
|
|
6
|
-
} from '@nextclaw/client-sdk';
|
|
1
|
+
import { useRef, useState } from "react";
|
|
2
|
+
import type { AppDataEntry, AppPackageOperationView, AppPackageView } from "@nextclaw/client-sdk";
|
|
7
3
|
import {
|
|
8
4
|
AlertCircle,
|
|
9
5
|
AppWindow,
|
|
@@ -22,29 +18,19 @@ import {
|
|
|
22
18
|
ShieldAlert,
|
|
23
19
|
ShieldCheck,
|
|
24
20
|
type LucideIcon,
|
|
25
|
-
} from
|
|
26
|
-
import { AppArtwork } from
|
|
27
|
-
import {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
} from
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
import
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
37
|
-
import {
|
|
38
|
-
Dialog,
|
|
39
|
-
DialogContent,
|
|
40
|
-
DialogDescription,
|
|
41
|
-
DialogFooter,
|
|
42
|
-
DialogHeader,
|
|
43
|
-
DialogTitle,
|
|
44
|
-
} from '@/shared/components/ui/dialog';
|
|
45
|
-
import { Popover, PopoverContent, PopoverTrigger } from '@/shared/components/ui/popover';
|
|
46
|
-
import { getLanguage, t } from '@/shared/lib/i18n';
|
|
47
|
-
import { cn } from '@/shared/lib/utils';
|
|
21
|
+
} from "lucide-react";
|
|
22
|
+
import { AppArtwork } from "@/features/apps/components/app-artwork";
|
|
23
|
+
import { AppDocumentAccessSection } from "@/features/apps/components/app-document-access-section";
|
|
24
|
+
import { AppDataRemovalChoice, AppStorageUsageDetails, formatBytes } from "@/features/app-data";
|
|
25
|
+
import { isAppPackageOperationActive } from "@/features/apps/hooks/use-app-packages";
|
|
26
|
+
import { readAppPackageAvailability } from "@/features/apps/utils/app-package-readiness.utils";
|
|
27
|
+
import type { PanelAppEntryView } from "@/shared/lib/api";
|
|
28
|
+
import { Button } from "@/shared/components/ui/button";
|
|
29
|
+
import { Card } from "@/shared/components/ui/card";
|
|
30
|
+
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/shared/components/ui/dialog";
|
|
31
|
+
import { Popover, PopoverContent, PopoverTrigger } from "@/shared/components/ui/popover";
|
|
32
|
+
import { getLanguage, t } from "@/shared/lib/i18n";
|
|
33
|
+
import { cn } from "@/shared/lib/utils";
|
|
48
34
|
|
|
49
35
|
export function AppPackageCard({
|
|
50
36
|
appPackage,
|
|
@@ -72,7 +58,7 @@ export function AppPackageCard({
|
|
|
72
58
|
onUninstall: (purgeData: boolean) => void;
|
|
73
59
|
onUpdate: () => void;
|
|
74
60
|
panelApps: PanelAppEntryView[];
|
|
75
|
-
storageUsage?: AppDataEntry[
|
|
61
|
+
storageUsage?: AppDataEntry["usage"];
|
|
76
62
|
storageUsageLoading: boolean;
|
|
77
63
|
storageUsageUnavailable: boolean;
|
|
78
64
|
unavailableMessage?: string;
|
|
@@ -82,24 +68,15 @@ export function AppPackageCard({
|
|
|
82
68
|
const [purgeData, setPurgeData] = useState(false);
|
|
83
69
|
const skipMenuFocusRestoreRef = useRef(false);
|
|
84
70
|
const uninstallCancelRef = useRef<HTMLButtonElement>(null);
|
|
85
|
-
const panelComponents = appPackage.components.filter((component) => component.kind ===
|
|
71
|
+
const panelComponents = appPackage.components.filter((component) => component.kind === "panel");
|
|
86
72
|
const serviceCount = appPackage.components.length - panelComponents.length;
|
|
87
|
-
const rollbackVersions = appPackage.installedVersions.filter(
|
|
88
|
-
(version) => version !== appPackage.activeVersion,
|
|
89
|
-
);
|
|
73
|
+
const rollbackVersions = appPackage.installedVersions.filter((version) => version !== appPackage.activeVersion);
|
|
90
74
|
const displayName = readLocalizedText(appPackage.name, appPackage.nameI18n) ?? appPackage.id;
|
|
91
|
-
const displayDescription = readLocalizedText(
|
|
92
|
-
appPackage.description,
|
|
93
|
-
appPackage.descriptionI18n,
|
|
94
|
-
);
|
|
75
|
+
const displayDescription = readLocalizedText(appPackage.description, appPackage.descriptionI18n);
|
|
95
76
|
const operationActive = operation ? isAppPackageOperationActive(operation.status) : false;
|
|
96
77
|
const pending = isPending || operationActive;
|
|
97
78
|
const availability = readAppPackageAvailability(appPackage, unavailableMessage);
|
|
98
|
-
const storageUsageLabel = resolveStorageUsageLabel(
|
|
99
|
-
storageUsage,
|
|
100
|
-
storageUsageLoading,
|
|
101
|
-
storageUsageUnavailable,
|
|
102
|
-
);
|
|
79
|
+
const storageUsageLabel = resolveStorageUsageLabel(storageUsage, storageUsageLoading, storageUsageUnavailable);
|
|
103
80
|
|
|
104
81
|
return (
|
|
105
82
|
<Card surface="flat" hover={false} className="overflow-hidden border-border/60 shadow-[0_1px_2px_rgba(0,0,0,0.025)]">
|
|
@@ -108,75 +85,51 @@ export function AppPackageCard({
|
|
|
108
85
|
<div className="min-w-0 flex-1">
|
|
109
86
|
<div className="flex min-w-0 items-center gap-2">
|
|
110
87
|
<h2 className="truncate text-sm font-semibold text-foreground">{displayName}</h2>
|
|
111
|
-
<span className={cn(
|
|
112
|
-
'shrink-0 rounded-full px-2 py-0.5 text-[10px] font-medium',
|
|
113
|
-
availability.className,
|
|
114
|
-
)}>
|
|
115
|
-
{availability.label}
|
|
116
|
-
</span>
|
|
88
|
+
<span className={cn("shrink-0 rounded-full px-2 py-0.5 text-[10px] font-medium", availability.className)}>{availability.label}</span>
|
|
117
89
|
</div>
|
|
118
90
|
{availability.message ? (
|
|
119
|
-
<p role="alert" className={cn(
|
|
91
|
+
<p role="alert" className={cn("mt-2 text-xs leading-5", availability.messageClassName)}>
|
|
120
92
|
{availability.message}
|
|
121
93
|
</p>
|
|
122
94
|
) : null}
|
|
123
|
-
{!appPackage.enabled && appPackage.readiness.status !==
|
|
95
|
+
{!appPackage.enabled && appPackage.readiness.status !== "ready" ? (
|
|
124
96
|
<ul className="mt-1.5 space-y-1 text-xs leading-5 text-muted-foreground">
|
|
125
97
|
{appPackage.readiness.requirements.map((requirement) => (
|
|
126
98
|
<li key={`${requirement.componentId}:${requirement.kind}:${requirement.id}`}>
|
|
127
99
|
{requirement.title}
|
|
128
|
-
{requirement.description ? ` — ${requirement.description}` :
|
|
100
|
+
{requirement.description ? ` — ${requirement.description}` : ""}
|
|
129
101
|
</li>
|
|
130
102
|
))}
|
|
131
103
|
</ul>
|
|
132
104
|
) : null}
|
|
133
|
-
{displayDescription ?
|
|
134
|
-
<p className="mt-1 line-clamp-2 text-xs leading-5 text-muted-foreground">
|
|
135
|
-
{displayDescription}
|
|
136
|
-
</p>
|
|
137
|
-
) : null}
|
|
105
|
+
{displayDescription ? <p className="mt-1 line-clamp-2 text-xs leading-5 text-muted-foreground">{displayDescription}</p> : null}
|
|
138
106
|
<div className="mt-1.5 flex flex-wrap items-center gap-x-2 gap-y-1 text-[11px] text-muted-foreground/75">
|
|
139
107
|
<span>v{appPackage.activeVersion}</span>
|
|
140
|
-
{appPackage.builtIn ? <span>{t(
|
|
108
|
+
{appPackage.builtIn ? <span>{t("appPackagesBuiltIn")}</span> : null}
|
|
141
109
|
{serviceCount > 0 ? (
|
|
142
110
|
<span className="inline-flex items-center gap-1">
|
|
143
111
|
<Server className="h-3 w-3" />
|
|
144
|
-
{t(
|
|
112
|
+
{t("appPackagesLocalService")}
|
|
145
113
|
</span>
|
|
146
114
|
) : null}
|
|
147
|
-
<span className={cn(
|
|
148
|
-
|
|
149
|
-
appPackage.isolation ===
|
|
150
|
-
)}>
|
|
151
|
-
{appPackage.isolation === 'full-user'
|
|
152
|
-
? <ShieldAlert className="h-3 w-3" />
|
|
153
|
-
: <ShieldCheck className="h-3 w-3" />}
|
|
154
|
-
{appPackage.isolation === 'full-user'
|
|
155
|
-
? t('appPackagesIsolationFullUser')
|
|
156
|
-
: appPackage.isolation === 'host-mediated'
|
|
157
|
-
? t('appPackagesIsolationMediated')
|
|
158
|
-
: t('appPackagesIsolationPanel')}
|
|
115
|
+
<span className={cn("inline-flex items-center gap-1", appPackage.isolation === "full-user" && "text-amber-700 dark:text-amber-300")}>
|
|
116
|
+
{appPackage.isolation === "full-user" ? <ShieldAlert className="h-3 w-3" /> : <ShieldCheck className="h-3 w-3" />}
|
|
117
|
+
{appPackage.isolation === "full-user" ? t("appPackagesIsolationFullUser") : appPackage.isolation === "host-mediated" ? t("appPackagesIsolationMediated") : t("appPackagesIsolationPanel")}
|
|
159
118
|
</span>
|
|
160
119
|
</div>
|
|
161
|
-
<div
|
|
162
|
-
className="mt-2 flex min-w-0 items-center gap-1.5 rounded-md bg-muted/35 px-2 py-1 text-[10px] text-muted-foreground"
|
|
163
|
-
title={appPackage.storage.dataDirectory}
|
|
164
|
-
>
|
|
120
|
+
<div className="mt-2 flex min-w-0 items-center gap-1.5 rounded-md bg-muted/35 px-2 py-1 text-[10px] text-muted-foreground" title={appPackage.storage.dataDirectory}>
|
|
165
121
|
<HardDrive className="h-3 w-3 shrink-0" />
|
|
166
122
|
<span className="shrink-0">
|
|
167
|
-
{t(
|
|
168
|
-
{storageUsageLabel}
|
|
123
|
+
{t("appPackagesDataUsage")} {storageUsageLabel}
|
|
169
124
|
</span>
|
|
170
|
-
<code className="min-w-0 truncate font-mono text-[10px]">
|
|
171
|
-
{appPackage.storage.dataDirectory}
|
|
172
|
-
</code>
|
|
125
|
+
<code className="min-w-0 truncate font-mono text-[10px]">{appPackage.storage.dataDirectory}</code>
|
|
173
126
|
</div>
|
|
174
127
|
</div>
|
|
175
128
|
<div className="flex shrink-0 items-center gap-1">
|
|
176
129
|
<Button
|
|
177
130
|
type="button"
|
|
178
131
|
size="sm"
|
|
179
|
-
variant={appPackage.enabled ?
|
|
132
|
+
variant={appPackage.enabled ? "outline" : "default"}
|
|
180
133
|
disabled={pending || !availability.interactive}
|
|
181
134
|
title={availability.message}
|
|
182
135
|
onClick={appPackage.enabled ? onDisable : onEnable}
|
|
@@ -189,65 +142,65 @@ export function AppPackageCard({
|
|
|
189
142
|
})}
|
|
190
143
|
</Button>
|
|
191
144
|
<Popover open={menuOpen} onOpenChange={setMenuOpen}>
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
>
|
|
200
|
-
<MoreHorizontal className="h-4 w-4" />
|
|
201
|
-
</button>
|
|
202
|
-
</PopoverTrigger>
|
|
203
|
-
<PopoverContent
|
|
204
|
-
align="end"
|
|
205
|
-
className="w-52 rounded-xl p-1.5"
|
|
206
|
-
onCloseAutoFocus={(event) => {
|
|
207
|
-
if (skipMenuFocusRestoreRef.current) {
|
|
208
|
-
event.preventDefault();
|
|
209
|
-
skipMenuFocusRestoreRef.current = false;
|
|
210
|
-
}
|
|
211
|
-
}}
|
|
145
|
+
<PopoverTrigger asChild>
|
|
146
|
+
<button
|
|
147
|
+
type="button"
|
|
148
|
+
aria-label={t("appPackagesMoreActions")}
|
|
149
|
+
title={t("appPackagesMoreActions")}
|
|
150
|
+
disabled={pending}
|
|
151
|
+
className="rounded-full p-2 text-muted-foreground transition-colors hover:bg-[var(--interaction-hover)] hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border disabled:opacity-50"
|
|
212
152
|
>
|
|
153
|
+
<MoreHorizontal className="h-4 w-4" />
|
|
154
|
+
</button>
|
|
155
|
+
</PopoverTrigger>
|
|
156
|
+
<PopoverContent
|
|
157
|
+
align="end"
|
|
158
|
+
className="w-52 rounded-xl p-1.5"
|
|
159
|
+
onCloseAutoFocus={(event) => {
|
|
160
|
+
if (skipMenuFocusRestoreRef.current) {
|
|
161
|
+
event.preventDefault();
|
|
162
|
+
skipMenuFocusRestoreRef.current = false;
|
|
163
|
+
}
|
|
164
|
+
}}
|
|
165
|
+
>
|
|
166
|
+
<AppPackageMenuItem
|
|
167
|
+
icon={RefreshCw}
|
|
168
|
+
label={t("appPackagesCheckUpdate")}
|
|
169
|
+
onClick={() => {
|
|
170
|
+
setMenuOpen(false);
|
|
171
|
+
onUpdate();
|
|
172
|
+
}}
|
|
173
|
+
/>
|
|
174
|
+
{rollbackVersions.map((version) => (
|
|
213
175
|
<AppPackageMenuItem
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
setMenuOpen(false);
|
|
218
|
-
onUpdate();
|
|
219
|
-
}}
|
|
220
|
-
/>
|
|
221
|
-
{rollbackVersions.map((version) => (
|
|
222
|
-
<AppPackageMenuItem
|
|
223
|
-
key={version}
|
|
224
|
-
icon={RotateCcw}
|
|
225
|
-
label={`${t('appPackagesRollback')} v${version}`}
|
|
226
|
-
onClick={() => {
|
|
227
|
-
setMenuOpen(false);
|
|
228
|
-
onRollback(version);
|
|
229
|
-
}}
|
|
230
|
-
/>
|
|
231
|
-
))}
|
|
232
|
-
<AppPackageMenuItem
|
|
233
|
-
destructive
|
|
234
|
-
icon={Trash2}
|
|
235
|
-
label={t('appPackagesUninstall')}
|
|
176
|
+
key={version}
|
|
177
|
+
icon={RotateCcw}
|
|
178
|
+
label={`${t("appPackagesRollback")} v${version}`}
|
|
236
179
|
onClick={() => {
|
|
237
|
-
skipMenuFocusRestoreRef.current = true;
|
|
238
180
|
setMenuOpen(false);
|
|
239
|
-
|
|
240
|
-
setUninstallOpen(true);
|
|
181
|
+
onRollback(version);
|
|
241
182
|
}}
|
|
242
183
|
/>
|
|
243
|
-
|
|
244
|
-
|
|
184
|
+
))}
|
|
185
|
+
<AppPackageMenuItem
|
|
186
|
+
destructive
|
|
187
|
+
icon={Trash2}
|
|
188
|
+
label={t("appPackagesUninstall")}
|
|
189
|
+
onClick={() => {
|
|
190
|
+
skipMenuFocusRestoreRef.current = true;
|
|
191
|
+
setMenuOpen(false);
|
|
192
|
+
setPurgeData(false);
|
|
193
|
+
setUninstallOpen(true);
|
|
194
|
+
}}
|
|
195
|
+
/>
|
|
196
|
+
</PopoverContent>
|
|
197
|
+
</Popover>
|
|
245
198
|
</div>
|
|
246
199
|
</div>
|
|
247
200
|
|
|
248
|
-
{operation && (operationActive || operation.status ===
|
|
249
|
-
|
|
250
|
-
|
|
201
|
+
{operation && (operationActive || operation.status === "failed" || operation.status === "interrupted") ? <PackageOperationStatus operation={operation} /> : null}
|
|
202
|
+
|
|
203
|
+
<AppDocumentAccessSection appId={appPackage.id} appName={displayName} disabled={pending || !availability.interactive} scopes={appPackage.documentAccess ?? []} />
|
|
251
204
|
|
|
252
205
|
{panelComponents.length > 0 ? (
|
|
253
206
|
<div className="border-t border-border/60 bg-muted/20 p-2">
|
|
@@ -255,33 +208,28 @@ export function AppPackageCard({
|
|
|
255
208
|
{panelComponents.map((component) => {
|
|
256
209
|
const panelApp = panelApps.find((entry) => entry.appId === component.id);
|
|
257
210
|
const Icon = resolveComponentIcon(component.id);
|
|
258
|
-
const componentTitle = readLocalizedText(component.title, component.titleI18n)
|
|
259
|
-
?? component.id;
|
|
211
|
+
const componentTitle = readLocalizedText(component.title, component.titleI18n) ?? component.id;
|
|
260
212
|
return (
|
|
261
213
|
<button
|
|
262
214
|
key={component.id}
|
|
263
215
|
type="button"
|
|
264
216
|
disabled={!appPackage.enabled || !panelApp || pending || !availability.interactive}
|
|
265
|
-
onClick={() =>
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
component.descriptionI18n,
|
|
271
|
-
)
|
|
272
|
-
}
|
|
217
|
+
onClick={() =>
|
|
218
|
+
panelApp &&
|
|
219
|
+
onOpenPanelApp({
|
|
220
|
+
...panelApp,
|
|
221
|
+
title: componentTitle,
|
|
222
|
+
description: readLocalizedText(component.description, component.descriptionI18n),
|
|
223
|
+
})
|
|
224
|
+
}
|
|
273
225
|
className="group flex min-w-0 items-center gap-2 rounded-lg px-2.5 py-2 text-left transition-colors hover:bg-[var(--interaction-hover)] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border disabled:cursor-default disabled:opacity-55"
|
|
274
|
-
title={availability.message ?? (!appPackage.enabled ? t(
|
|
226
|
+
title={availability.message ?? (!appPackage.enabled ? t("appPackagesEnableToOpen") : componentTitle)}
|
|
275
227
|
>
|
|
276
228
|
<span className="flex h-7 w-7 shrink-0 items-center justify-center rounded-lg bg-card text-muted-foreground shadow-sm ring-1 ring-border/60">
|
|
277
229
|
<ComponentIcon icon={component.icon} fallback={Icon} />
|
|
278
230
|
</span>
|
|
279
|
-
<span className="min-w-0 flex-1 truncate text-xs font-medium text-foreground">
|
|
280
|
-
|
|
281
|
-
</span>
|
|
282
|
-
{appPackage.enabled ? (
|
|
283
|
-
<ChevronRight className="h-3.5 w-3.5 shrink-0 text-muted-foreground/50 transition-transform group-hover:translate-x-0.5" />
|
|
284
|
-
) : null}
|
|
231
|
+
<span className="min-w-0 flex-1 truncate text-xs font-medium text-foreground">{componentTitle}</span>
|
|
232
|
+
{appPackage.enabled ? <ChevronRight className="h-3.5 w-3.5 shrink-0 text-muted-foreground/50 transition-transform group-hover:translate-x-0.5" /> : null}
|
|
285
233
|
</button>
|
|
286
234
|
);
|
|
287
235
|
})}
|
|
@@ -298,41 +246,19 @@ export function AppPackageCard({
|
|
|
298
246
|
}}
|
|
299
247
|
>
|
|
300
248
|
<DialogHeader>
|
|
301
|
-
<DialogTitle>{t(
|
|
302
|
-
<DialogDescription>{t(
|
|
249
|
+
<DialogTitle>{t("appPackagesUninstallTitle")}</DialogTitle>
|
|
250
|
+
<DialogDescription>{t("appPackagesUninstallDescription")}</DialogDescription>
|
|
303
251
|
</DialogHeader>
|
|
304
252
|
<div className="mt-4 grid gap-2">
|
|
305
|
-
<AppDataRemovalChoice
|
|
306
|
-
|
|
307
|
-
description={t('appPackagesKeepDataDescription')}
|
|
308
|
-
label={t('appPackagesKeepData')}
|
|
309
|
-
onClick={() => setPurgeData(false)}
|
|
310
|
-
/>
|
|
311
|
-
<AppDataRemovalChoice
|
|
312
|
-
checked={purgeData}
|
|
313
|
-
destructive
|
|
314
|
-
description={t('appPackagesDeleteDataDescription')}
|
|
315
|
-
label={t('appPackagesDeleteData')}
|
|
316
|
-
onClick={() => setPurgeData(true)}
|
|
317
|
-
/>
|
|
253
|
+
<AppDataRemovalChoice checked={!purgeData} description={t("appPackagesKeepDataDescription")} label={t("appPackagesKeepData")} onClick={() => setPurgeData(false)} />
|
|
254
|
+
<AppDataRemovalChoice checked={purgeData} destructive description={t("appPackagesDeleteDataDescription")} label={t("appPackagesDeleteData")} onClick={() => setPurgeData(true)} />
|
|
318
255
|
</div>
|
|
319
256
|
<div className="mt-3">
|
|
320
|
-
<AppPackageStorageUsage
|
|
321
|
-
loading={storageUsageLoading}
|
|
322
|
-
storage={appPackage.storage}
|
|
323
|
-
usage={storageUsage}
|
|
324
|
-
unavailable={storageUsageUnavailable}
|
|
325
|
-
/>
|
|
257
|
+
<AppPackageStorageUsage loading={storageUsageLoading} storage={appPackage.storage} usage={storageUsage} unavailable={storageUsageUnavailable} />
|
|
326
258
|
</div>
|
|
327
259
|
<DialogFooter className="mt-5 gap-2 sm:gap-0">
|
|
328
|
-
<Button
|
|
329
|
-
|
|
330
|
-
autoFocus
|
|
331
|
-
type="button"
|
|
332
|
-
variant="outline"
|
|
333
|
-
onClick={() => setUninstallOpen(false)}
|
|
334
|
-
>
|
|
335
|
-
{t('cancel')}
|
|
260
|
+
<Button ref={uninstallCancelRef} autoFocus type="button" variant="outline" onClick={() => setUninstallOpen(false)}>
|
|
261
|
+
{t("cancel")}
|
|
336
262
|
</Button>
|
|
337
263
|
<Button
|
|
338
264
|
type="button"
|
|
@@ -342,7 +268,7 @@ export function AppPackageCard({
|
|
|
342
268
|
setUninstallOpen(false);
|
|
343
269
|
}}
|
|
344
270
|
>
|
|
345
|
-
{t(
|
|
271
|
+
{t("appPackagesUninstall")}
|
|
346
272
|
</Button>
|
|
347
273
|
</DialogFooter>
|
|
348
274
|
</DialogContent>
|
|
@@ -351,85 +277,45 @@ export function AppPackageCard({
|
|
|
351
277
|
);
|
|
352
278
|
}
|
|
353
279
|
|
|
354
|
-
function AppPackageStorageUsage({
|
|
355
|
-
loading,
|
|
356
|
-
storage,
|
|
357
|
-
unavailable,
|
|
358
|
-
usage,
|
|
359
|
-
}: {
|
|
360
|
-
loading: boolean;
|
|
361
|
-
storage: AppPackageView['storage'];
|
|
362
|
-
unavailable: boolean;
|
|
363
|
-
usage?: AppDataEntry['usage'];
|
|
364
|
-
}) {
|
|
280
|
+
function AppPackageStorageUsage({ loading, storage, unavailable, usage }: { loading: boolean; storage: AppPackageView["storage"]; unavailable: boolean; usage?: AppDataEntry["usage"] }) {
|
|
365
281
|
if (usage) {
|
|
366
282
|
return <AppStorageUsageDetails storage={storage} usage={usage} />;
|
|
367
283
|
}
|
|
368
284
|
return (
|
|
369
285
|
<div className="rounded-xl bg-muted/45 px-3 py-3 text-xs text-muted-foreground">
|
|
370
|
-
{loading ?
|
|
371
|
-
|
|
372
|
-
) : null}
|
|
373
|
-
{t(loading
|
|
374
|
-
? 'appPackagesLoading'
|
|
375
|
-
: unavailable ? 'appPackagesDataUsageUnavailable' : 'appPackagesNoDataYet')}
|
|
286
|
+
{loading ? <LoaderCircle className="mr-1.5 inline h-3.5 w-3.5 animate-spin" /> : null}
|
|
287
|
+
{t(loading ? "appPackagesLoading" : unavailable ? "appPackagesDataUsageUnavailable" : "appPackagesNoDataYet")}
|
|
376
288
|
</div>
|
|
377
289
|
);
|
|
378
290
|
}
|
|
379
291
|
|
|
380
|
-
function resolveStorageUsageLabel(
|
|
381
|
-
usage: AppDataEntry['usage'] | undefined,
|
|
382
|
-
loading: boolean,
|
|
383
|
-
unavailable: boolean,
|
|
384
|
-
): string {
|
|
292
|
+
function resolveStorageUsageLabel(usage: AppDataEntry["usage"] | undefined, loading: boolean, unavailable: boolean): string {
|
|
385
293
|
if (usage) return formatBytes(usage.totalBytes);
|
|
386
|
-
if (loading) return t(
|
|
387
|
-
return t(unavailable ?
|
|
294
|
+
if (loading) return t("appPackagesLoading");
|
|
295
|
+
return t(unavailable ? "appPackagesDataUsageUnavailable" : "appPackagesNoDataYet");
|
|
388
296
|
}
|
|
389
297
|
|
|
390
|
-
function renderPrimaryActionLabel({
|
|
391
|
-
enabled,
|
|
392
|
-
isPending,
|
|
393
|
-
operation,
|
|
394
|
-
operationActive,
|
|
395
|
-
}: {
|
|
396
|
-
enabled: boolean;
|
|
397
|
-
isPending: boolean;
|
|
398
|
-
operation?: AppPackageOperationView;
|
|
399
|
-
operationActive: boolean;
|
|
400
|
-
}) {
|
|
298
|
+
function renderPrimaryActionLabel({ enabled, isPending, operation, operationActive }: { enabled: boolean; isPending: boolean; operation?: AppPackageOperationView; operationActive: boolean }) {
|
|
401
299
|
if (operationActive) {
|
|
402
300
|
return (
|
|
403
301
|
<>
|
|
404
302
|
<LoaderCircle className="mr-1.5 h-3.5 w-3.5 animate-spin" />
|
|
405
|
-
{operation ? operationLabel(operation) : t(
|
|
303
|
+
{operation ? operationLabel(operation) : t("appPackagesWorking")}
|
|
406
304
|
</>
|
|
407
305
|
);
|
|
408
306
|
}
|
|
409
|
-
if (isPending) return t(
|
|
410
|
-
return enabled ? t(
|
|
307
|
+
if (isPending) return t("appPackagesWorking");
|
|
308
|
+
return enabled ? t("appPackagesDisable") : t("appPackagesEnable");
|
|
411
309
|
}
|
|
412
310
|
|
|
413
|
-
function AppPackageMenuItem({
|
|
414
|
-
destructive = false,
|
|
415
|
-
icon: Icon,
|
|
416
|
-
label,
|
|
417
|
-
onClick,
|
|
418
|
-
}: {
|
|
419
|
-
destructive?: boolean;
|
|
420
|
-
icon: LucideIcon;
|
|
421
|
-
label: string;
|
|
422
|
-
onClick: () => void;
|
|
423
|
-
}) {
|
|
311
|
+
function AppPackageMenuItem({ destructive = false, icon: Icon, label, onClick }: { destructive?: boolean; icon: LucideIcon; label: string; onClick: () => void }) {
|
|
424
312
|
return (
|
|
425
313
|
<button
|
|
426
314
|
type="button"
|
|
427
315
|
onClick={onClick}
|
|
428
316
|
className={cn(
|
|
429
|
-
|
|
430
|
-
destructive
|
|
431
|
-
? 'text-destructive hover:bg-destructive/10'
|
|
432
|
-
: 'text-muted-foreground hover:bg-[var(--interaction-hover)] hover:text-accent-foreground',
|
|
317
|
+
"flex w-full items-center gap-2 rounded-lg px-2.5 py-2 text-left text-sm transition-colors",
|
|
318
|
+
destructive ? "text-destructive hover:bg-destructive/10" : "text-muted-foreground hover:bg-[var(--interaction-hover)] hover:text-accent-foreground",
|
|
433
319
|
)}
|
|
434
320
|
>
|
|
435
321
|
<Icon className="h-4 w-4 shrink-0" />
|
|
@@ -438,19 +324,8 @@ function AppPackageMenuItem({
|
|
|
438
324
|
);
|
|
439
325
|
}
|
|
440
326
|
|
|
441
|
-
function ComponentIcon({
|
|
442
|
-
|
|
443
|
-
icon,
|
|
444
|
-
}: {
|
|
445
|
-
fallback: LucideIcon;
|
|
446
|
-
icon?: string;
|
|
447
|
-
}) {
|
|
448
|
-
if (icon && (
|
|
449
|
-
icon.startsWith('data:') ||
|
|
450
|
-
icon.startsWith('/') ||
|
|
451
|
-
icon.startsWith('http://') ||
|
|
452
|
-
icon.startsWith('https://')
|
|
453
|
-
)) {
|
|
327
|
+
function ComponentIcon({ fallback: Fallback, icon }: { fallback: LucideIcon; icon?: string }) {
|
|
328
|
+
if (icon && (icon.startsWith("data:") || icon.startsWith("/") || icon.startsWith("http://") || icon.startsWith("https://"))) {
|
|
454
329
|
return <img src={icon} alt="" className="h-4 w-4 rounded object-cover" />;
|
|
455
330
|
}
|
|
456
331
|
if (icon) {
|
|
@@ -460,21 +335,16 @@ function ComponentIcon({
|
|
|
460
335
|
}
|
|
461
336
|
|
|
462
337
|
function PackageOperationStatus({ operation }: { operation: AppPackageOperationView }) {
|
|
463
|
-
const failed = operation.status ===
|
|
338
|
+
const failed = operation.status === "failed" || operation.status === "interrupted";
|
|
464
339
|
const progress = Math.max(4, Math.round((operation.completedSteps / operation.totalSteps) * 100));
|
|
465
340
|
return (
|
|
466
|
-
<div className="border-t border-border/50 px-4 py-2.5" role={failed ?
|
|
341
|
+
<div className="border-t border-border/50 px-4 py-2.5" role={failed ? "alert" : "status"} aria-live="polite">
|
|
467
342
|
<div className="flex items-center gap-2 text-[11px]">
|
|
468
343
|
{failed ? <AlertCircle className="h-3.5 w-3.5 text-destructive" /> : <LoaderCircle className="h-3.5 w-3.5 animate-spin text-primary" />}
|
|
469
|
-
<span className={failed ?
|
|
470
|
-
{failed ? operation.error ?? t('appPackagesActionFailed') : operationLabel(operation)}
|
|
471
|
-
</span>
|
|
344
|
+
<span className={failed ? "text-destructive" : "text-muted-foreground"}>{failed ? (operation.error ?? t("appPackagesActionFailed")) : operationLabel(operation)}</span>
|
|
472
345
|
</div>
|
|
473
346
|
<div className="mt-2 h-1 overflow-hidden rounded-full bg-muted">
|
|
474
|
-
<div
|
|
475
|
-
className={cn('h-full rounded-full transition-[width] duration-300', failed ? 'bg-destructive' : 'bg-primary')}
|
|
476
|
-
style={{ width: failed ? '100%' : `${progress}%` }}
|
|
477
|
-
/>
|
|
347
|
+
<div className={cn("h-full rounded-full transition-[width] duration-300", failed ? "bg-destructive" : "bg-primary")} style={{ width: failed ? "100%" : `${progress}%` }} />
|
|
478
348
|
</div>
|
|
479
349
|
</div>
|
|
480
350
|
);
|
|
@@ -482,34 +352,39 @@ function PackageOperationStatus({ operation }: { operation: AppPackageOperationV
|
|
|
482
352
|
|
|
483
353
|
function operationLabel(operation: AppPackageOperationView): string {
|
|
484
354
|
switch (operation.status) {
|
|
485
|
-
case
|
|
486
|
-
|
|
487
|
-
case
|
|
488
|
-
|
|
489
|
-
case
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
355
|
+
case "queued":
|
|
356
|
+
return t("appPackagesPreparing");
|
|
357
|
+
case "resolving":
|
|
358
|
+
return t("appPackagesResolving");
|
|
359
|
+
case "downloading":
|
|
360
|
+
return t("appPackagesDownloading");
|
|
361
|
+
case "verifying":
|
|
362
|
+
return t("appPackagesVerifying");
|
|
363
|
+
case "installing":
|
|
364
|
+
return operation.action === "rollback"
|
|
365
|
+
? `${t("appPackagesSwitchingVersion")} ${operation.targetVersion ? `v${operation.targetVersion}` : ""}`.trim()
|
|
366
|
+
: operation.action === "uninstall"
|
|
367
|
+
? t("appPackagesUninstalling")
|
|
368
|
+
: t("appPackagesInstalling");
|
|
369
|
+
case "finalizing":
|
|
370
|
+
return t("appPackagesFinalizing");
|
|
371
|
+
case "succeeded":
|
|
372
|
+
return t("appPackagesCompleted");
|
|
373
|
+
case "failed":
|
|
374
|
+
case "interrupted":
|
|
375
|
+
return t("appPackagesActionFailed");
|
|
498
376
|
}
|
|
499
377
|
}
|
|
500
378
|
|
|
501
379
|
function resolveComponentIcon(componentId: string): LucideIcon {
|
|
502
|
-
if (componentId.endsWith(
|
|
503
|
-
if (componentId.endsWith(
|
|
504
|
-
if (componentId.endsWith(
|
|
505
|
-
if (componentId.endsWith(
|
|
380
|
+
if (componentId.endsWith("-todos")) return CheckSquare2;
|
|
381
|
+
if (componentId.endsWith("-notes")) return NotebookText;
|
|
382
|
+
if (componentId.endsWith("-favorites")) return Bookmark;
|
|
383
|
+
if (componentId.endsWith("-calendar")) return CalendarDays;
|
|
506
384
|
return AppWindow;
|
|
507
385
|
}
|
|
508
386
|
|
|
509
|
-
function readLocalizedText(
|
|
510
|
-
|
|
511
|
-
localized: Record<string, string> | undefined,
|
|
512
|
-
): string | undefined {
|
|
513
|
-
const languageTag = getLanguage() === 'zh' ? 'zh-CN' : 'en-US';
|
|
387
|
+
function readLocalizedText(fallback: string | undefined, localized: Record<string, string> | undefined): string | undefined {
|
|
388
|
+
const languageTag = getLanguage() === "zh" ? "zh-CN" : "en-US";
|
|
514
389
|
return localized?.[languageTag] ?? fallback;
|
|
515
390
|
}
|