@nextclaw/ui 0.12.26 → 0.12.27
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/dist/assets/{channels-list-page-HgLgrEg4.js → channels-list-page-DkPvpAqc.js} +1 -1
- package/dist/assets/{chat-page-DAKMFDrS.js → chat-page-b7Zf32fF.js} +1 -1
- package/dist/assets/index-DmWo8dX2.css +1 -0
- package/dist/assets/{index-Cuwst6cc.js → index-DqJ3CYwi.js} +2 -2
- package/dist/assets/marketplace-page-BVqFjnEB.js +105 -0
- package/dist/assets/marketplace-page-DkQ2hTs1.js +1 -0
- package/dist/assets/{mcp-marketplace-page-DwnaLNTx.js → mcp-marketplace-page-BOYJO0kp.js} +1 -1
- package/dist/assets/mcp-marketplace-page-DSML7NN0.js +1 -0
- package/dist/assets/{model-config-L2l6YAlQ.js → model-config-Bg2yycmn.js} +1 -1
- package/dist/assets/{providers-list-DYAEunOp.js → providers-list-DC1q3fvC.js} +1 -1
- package/dist/assets/{runtime-config-page-BdeU8PEK.js → runtime-config-page-q-nC0C5i.js} +1 -1
- package/dist/assets/{search-config-CQUhd5RU.js → search-config-CcKHif8O.js} +1 -1
- package/dist/assets/{secrets-config-D-NWlW9q.js → secrets-config-DSg6O92a.js} +1 -1
- package/dist/assets/{use-infinite-scroll-loader-CFVdPpNv.js → use-infinite-scroll-loader-DF2e6nQ2.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +6 -6
- package/src/features/marketplace/components/curated-shelves/marketplace-curated-scene-route.test.tsx +54 -16
- package/src/features/marketplace/components/curated-shelves/marketplace-curated-shelves.tsx +96 -24
- package/src/features/marketplace/components/marketplace-page.test.tsx +4 -0
- package/src/features/marketplace/components/marketplace-page.tsx +16 -12
- package/src/features/marketplace/hooks/use-marketplace-curated-scene-route.ts +14 -5
- package/dist/assets/index-dlcqieQ0.css +0 -1
- package/dist/assets/marketplace-page-BeFbwxR-.js +0 -105
- package/dist/assets/marketplace-page-CR4xq-TM.js +0 -1
- package/dist/assets/mcp-marketplace-page-DlRrSCj3.js +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{_ as e,m as t,p as n,r}from"./i18n-D1144VAA.js";import{$t as i,Bt as a,Qt as o,Zt as s,en as c}from"./api-DGD9_Bg4.js";import{t as l}from"./createLucideIcon-DzY6wN61.js";import{a as u,i as d,n as f,r as p,t as m}from"./select-BUTwE_lC.js";import{u as h}from"./index-
|
|
1
|
+
import{_ as e,m as t,p as n,r}from"./i18n-D1144VAA.js";import{$t as i,Bt as a,Qt as o,Zt as s,en as c}from"./api-DGD9_Bg4.js";import{t as l}from"./createLucideIcon-DzY6wN61.js";import{a as u,i as d,n as f,r as p,t as m}from"./select-BUTwE_lC.js";import{u as h}from"./index-DqJ3CYwi.js";var g=class extends c{constructor(e,t){super(e,t)}bindMethods(){super.bindMethods(),this.fetchNextPage=this.fetchNextPage.bind(this),this.fetchPreviousPage=this.fetchPreviousPage.bind(this)}setOptions(e){super.setOptions({...e,behavior:i()})}getOptimisticResult(e){return e.behavior=i(),super.getOptimisticResult(e)}fetchNextPage(e){return this.fetch({...e,meta:{fetchMore:{direction:`forward`}}})}fetchPreviousPage(e){return this.fetch({...e,meta:{fetchMore:{direction:`backward`}}})}createResult(e,t){let{state:n}=e,r=super.createResult(e,t),{isFetching:i,isRefetching:a,isError:c,isRefetchError:l}=r,u=n.fetchMeta?.fetchMore?.direction,d=c&&u===`forward`,f=i&&u===`forward`,p=c&&u===`backward`,m=i&&u===`backward`;return{...r,fetchNextPage:this.fetchNextPage,fetchPreviousPage:this.fetchPreviousPage,hasNextPage:s(t,n.data),hasPreviousPage:o(t,n.data),isFetchNextPageError:d,isFetchingNextPage:f,isFetchPreviousPageError:p,isFetchingPreviousPage:m,isRefetchError:l&&!d&&!p,isRefetching:a&&!f&&!m}}};function _(e,t){return a(e,g,t)}var v=l(`PackageSearch`,[[`path`,{d:`M21 10V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l2-1.14`,key:`e7tb2h`}],[`path`,{d:`m7.5 4.27 9 5.15`,key:`1c824w`}],[`polyline`,{points:`3.29 7 12 12 20.71 7`,key:`ousv84`}],[`line`,{x1:`12`,x2:`12`,y1:`22`,y2:`12`,key:`a4e8g8`}],[`circle`,{cx:`18.5`,cy:`15.5`,r:`2.5`,key:`b5zd12`}],[`path`,{d:`M20.27 17.27 22 19`,key:`1l4muz`}]]);function y(e){if(!e||e.pages.length===0)return;let t=e.pages.flatMap(e=>e.items);return{...e.pages[e.pages.length-1],items:t,pages:e.pages,loadedItems:t.length,loadedPages:e.pages.length}}var b=n();function x({scope:e,searchText:t,searchPlaceholder:n,sort:i,onSearchTextChange:a,onSortChange:o}){return(0,b.jsx)(`div`,{className:`mb-4`,children:(0,b.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,b.jsxs)(`div`,{className:`relative min-w-0 flex-1`,children:[(0,b.jsx)(v,{className:`absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-400`}),(0,b.jsx)(`input`,{value:t,onChange:e=>a(e.target.value),placeholder:n,className:`h-9 w-full rounded-xl border border-gray-200/80 pl-9 pr-3 text-sm focus:outline-none focus:ring-1 focus:ring-primary/40`})]}),e===`all`&&(0,b.jsxs)(m,{value:i,onValueChange:e=>o(e),children:[(0,b.jsx)(d,{className:`h-9 w-[150px] shrink-0 rounded-lg`,children:(0,b.jsx)(u,{})}),(0,b.jsxs)(f,{children:[(0,b.jsx)(p,{value:`relevance`,children:r(`marketplaceSortRelevance`)}),(0,b.jsx)(p,{value:`updated`,children:r(`marketplaceSortUpdated`)})]})]})]})})}function S({count:e}){return(0,b.jsx)(b.Fragment,{children:Array.from({length:e},(e,t)=>(0,b.jsx)(`article`,{className:`h-full rounded-2xl border border-gray-200/40 bg-white px-5 py-4 shadow-sm`,children:(0,b.jsxs)(`div`,{className:`flex items-start justify-between gap-3.5`,children:[(0,b.jsxs)(`div`,{className:`flex min-w-0 flex-1 gap-3`,children:[(0,b.jsx)(h,{className:`h-10 w-10 shrink-0 rounded-xl`}),(0,b.jsxs)(`div`,{className:`min-w-0 flex-1 space-y-2 pt-0.5`,children:[(0,b.jsx)(h,{className:`h-4 w-32 max-w-[70%]`}),(0,b.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,b.jsx)(h,{className:`h-3 w-12`}),(0,b.jsx)(h,{className:`h-3 w-24`})]}),(0,b.jsx)(h,{className:`h-3 w-full`})]})]}),(0,b.jsx)(h,{className:`h-8 w-20 shrink-0 rounded-xl`})]})},`marketplace-skeleton-${t}`))})}function C({hasMore:e,loading:t,sentinelRef:n}){return!e&&!t?null:(0,b.jsxs)(`div`,{className:`py-4`,children:[e&&(0,b.jsx)(`div`,{ref:n,className:`h-1 w-full`,"aria-hidden":`true`}),t&&(0,b.jsx)(`div`,{"data-testid":`marketplace-loading-more`,className:`pt-3 text-center text-xs text-gray-500`,children:r(`loading`)})]})}function w(e){let t=e.trim().toLowerCase().replace(/_/g,`-`),n=[t,t.split(`-`)[0],`en`];return Array.from(new Set(n.filter(Boolean)))}function T(e){return e.trim().toLowerCase().replace(/_/g,`-`)}function E(e,t,n){if(e){let t=Object.entries(e).map(([e,t])=>({locale:T(e),text:typeof t==`string`?t.trim():``})).filter(e=>e.text.length>0);if(t.length>0){let e=new Map(t.map(e=>[e.locale,e.text]));for(let t of n){let n=T(t),r=e.get(n);if(r)return r}for(let e of n){let n=T(e).split(`-`)[0];if(!n)continue;let r=t.find(e=>e.locale===n||e.locale.startsWith(`${n}-`));if(r)return r.text}return t[0]?.text??``}}return t?.trim()??``}function D(e,t){if(!e)return``;for(let n of t)if(T(n).split(`-`)[0]===`zh`&&e.descriptionZh?.trim())return e.descriptionZh.trim();return e.description?.trim()?e.description.trim():e.descriptionZh?.trim()?e.descriptionZh.trim():``}var O=e(t(),1),k=160;function A(e){let t=(0,O.useRef)(null),n=(0,O.useRef)(null),r=(0,O.useRef)(e.onLoadMore),i=(0,O.useRef)(!1);return(0,O.useEffect)(()=>{r.current=e.onLoadMore},[e.onLoadMore]),(0,O.useEffect)(()=>{e.disabled&&(i.current=!1)},[e.disabled]),(0,O.useEffect)(()=>{let a=t.current,o=n.current,s=e.thresholdPx??k;if(e.disabled||!a||!o)return;let c=()=>{i.current||e.disabled||(i.current=!0,Promise.resolve(r.current()).finally(()=>{i.current=!1}))},l=()=>{o.getBoundingClientRect().top-a.getBoundingClientRect().bottom<=s&&c()};if(typeof IntersectionObserver==`function`){let e=new IntersectionObserver(e=>{e.some(e=>e.isIntersecting)&&c()},{root:a,rootMargin:`0px 0px ${s}px 0px`});return e.observe(o),l(),()=>{e.disconnect()}}return a.addEventListener(`scroll`,l,{passive:!0}),l(),()=>{a.removeEventListener(`scroll`,l)}},[e.disabled,e.thresholdPx,e.watchValue]),{containerRef:t,sentinelRef:n}}export{x as a,y as c,E as i,_ as l,w as n,C as o,D as r,S as s,A as t};
|
package/dist/index.html
CHANGED
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
})();
|
|
79
79
|
</script>
|
|
80
80
|
<title>NextClaw</title>
|
|
81
|
-
<script type="module" crossorigin src="/assets/index-
|
|
81
|
+
<script type="module" crossorigin src="/assets/index-DqJ3CYwi.js"></script>
|
|
82
82
|
<link rel="modulepreload" crossorigin href="/assets/i18n-D1144VAA.js">
|
|
83
83
|
<link rel="modulepreload" crossorigin href="/assets/createLucideIcon-DzY6wN61.js">
|
|
84
84
|
<link rel="modulepreload" crossorigin href="/assets/cpu-DPPwMzoC.js">
|
|
@@ -106,7 +106,7 @@
|
|
|
106
106
|
<link rel="modulepreload" crossorigin href="/assets/doc-browser-CAhfnm0D.js">
|
|
107
107
|
<link rel="modulepreload" crossorigin href="/assets/use-config-Cyv5IuSt.js">
|
|
108
108
|
<link rel="modulepreload" crossorigin href="/assets/desktop-DVUbOWbR.js">
|
|
109
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
109
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DmWo8dX2.css">
|
|
110
110
|
</head>
|
|
111
111
|
|
|
112
112
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextclaw/ui",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.27",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,14 +28,14 @@
|
|
|
28
28
|
"tailwind-merge": "^2.5.4",
|
|
29
29
|
"zod": "^3.23.8",
|
|
30
30
|
"zustand": "^5.0.2",
|
|
31
|
-
"@nextclaw/
|
|
32
|
-
"@nextclaw/
|
|
31
|
+
"@nextclaw/agent-chat": "0.1.16",
|
|
32
|
+
"@nextclaw/agent-chat-ui": "0.3.18",
|
|
33
33
|
"@nextclaw/ncp-http-agent-client": "0.3.23",
|
|
34
|
+
"@nextclaw/ncp": "0.5.11",
|
|
35
|
+
"@nextclaw/ncp-react": "0.4.31",
|
|
34
36
|
"@nextclaw/server": "0.12.18",
|
|
35
37
|
"@nextclaw/shared": "0.1.5",
|
|
36
|
-
"@nextclaw/
|
|
37
|
-
"@nextclaw/agent-chat": "0.1.16",
|
|
38
|
-
"@nextclaw/ncp-react": "0.4.31"
|
|
38
|
+
"@nextclaw/client-sdk": "0.1.6"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@testing-library/react": "^16.3.0",
|
package/src/features/marketplace/components/curated-shelves/marketplace-curated-scene-route.test.tsx
CHANGED
|
@@ -18,11 +18,27 @@ type ItemsQueryState = {
|
|
|
18
18
|
fetchNextPage?: () => Promise<unknown>;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
type ScenesQueryState = {
|
|
22
|
+
data?: {
|
|
23
|
+
scenes: Array<{
|
|
24
|
+
scene: string;
|
|
25
|
+
title: string;
|
|
26
|
+
description?: string;
|
|
27
|
+
count?: number;
|
|
28
|
+
}>;
|
|
29
|
+
};
|
|
30
|
+
isLoading: boolean;
|
|
31
|
+
isFetching: boolean;
|
|
32
|
+
isError: boolean;
|
|
33
|
+
error: Error | null;
|
|
34
|
+
};
|
|
35
|
+
|
|
21
36
|
const mocks = vi.hoisted(() => ({
|
|
22
37
|
navigate: vi.fn(),
|
|
23
38
|
docOpen: vi.fn(),
|
|
24
39
|
routeParams: {} as { scene?: string },
|
|
25
40
|
itemsQuery: null as unknown as ItemsQueryState,
|
|
41
|
+
scenesQuery: null as unknown as ScenesQueryState,
|
|
26
42
|
}));
|
|
27
43
|
|
|
28
44
|
vi.mock("react-router-dom", async () => {
|
|
@@ -55,21 +71,7 @@ vi.mock("@/shared/hooks/use-confirm-dialog", () => ({
|
|
|
55
71
|
|
|
56
72
|
vi.mock("@/features/marketplace/hooks/use-marketplace", () => ({
|
|
57
73
|
useMarketplaceItems: () => mocks.itemsQuery,
|
|
58
|
-
useMarketplaceSkillScenes: () =>
|
|
59
|
-
data: {
|
|
60
|
-
scenes: [
|
|
61
|
-
{
|
|
62
|
-
scene: "development-debugging",
|
|
63
|
-
title: "Development",
|
|
64
|
-
description: "Review, debug, analyze, and verify delivery work.",
|
|
65
|
-
},
|
|
66
|
-
],
|
|
67
|
-
},
|
|
68
|
-
isLoading: false,
|
|
69
|
-
isFetching: false,
|
|
70
|
-
isError: false,
|
|
71
|
-
error: null,
|
|
72
|
-
}),
|
|
74
|
+
useMarketplaceSkillScenes: () => mocks.scenesQuery,
|
|
73
75
|
useMarketplaceSkillSceneCounts: () => new Map([["development-debugging", 2]]),
|
|
74
76
|
useMarketplaceInstalled: () => ({
|
|
75
77
|
data: {
|
|
@@ -165,12 +167,32 @@ function createItemsQuery(items: MarketplaceItemSummary[]) {
|
|
|
165
167
|
};
|
|
166
168
|
}
|
|
167
169
|
|
|
170
|
+
function createScenesQuery(overrides: Partial<ScenesQueryState> = {}) {
|
|
171
|
+
return {
|
|
172
|
+
data: {
|
|
173
|
+
scenes: [
|
|
174
|
+
{
|
|
175
|
+
scene: "development-debugging",
|
|
176
|
+
title: "Development",
|
|
177
|
+
description: "Review, debug, analyze, and verify delivery work.",
|
|
178
|
+
},
|
|
179
|
+
],
|
|
180
|
+
},
|
|
181
|
+
isLoading: false,
|
|
182
|
+
isFetching: false,
|
|
183
|
+
isError: false,
|
|
184
|
+
error: null,
|
|
185
|
+
...overrides,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
168
189
|
describe("Marketplace curated scene routes", () => {
|
|
169
190
|
beforeEach(() => {
|
|
170
191
|
mocks.navigate.mockReset();
|
|
171
192
|
mocks.docOpen.mockReset();
|
|
172
193
|
mocks.routeParams = {};
|
|
173
194
|
mocks.itemsQuery = createItemsQuery(createSceneItems());
|
|
195
|
+
mocks.scenesQuery = createScenesQuery();
|
|
174
196
|
});
|
|
175
197
|
|
|
176
198
|
it("opens curated goals through a scene route", async () => {
|
|
@@ -183,6 +205,7 @@ describe("Marketplace curated scene routes", () => {
|
|
|
183
205
|
expect(mocks.navigate).toHaveBeenCalledWith(
|
|
184
206
|
"/skills/scenes/development-debugging",
|
|
185
207
|
);
|
|
208
|
+
expect(screen.getByText("All Skills")).toBeTruthy();
|
|
186
209
|
expect(container.querySelector("input")?.getAttribute("value") ?? "").toBe("");
|
|
187
210
|
});
|
|
188
211
|
|
|
@@ -196,7 +219,7 @@ describe("Marketplace curated scene routes", () => {
|
|
|
196
219
|
expect(screen.getByRole("button", { name: "Back" })).toBeTruthy();
|
|
197
220
|
expect(screen.getByText("Code Review")).toBeTruthy();
|
|
198
221
|
expect(screen.queryByText("Calendar Sync")).toBeNull();
|
|
199
|
-
expect(screen.queryByText("
|
|
222
|
+
expect(screen.queryByText("All Skills")).toBeNull();
|
|
200
223
|
expect(screen.queryByPlaceholderText("Search skills...")).toBeNull();
|
|
201
224
|
|
|
202
225
|
await user.click(screen.getByRole("button", { name: "Back" }));
|
|
@@ -232,4 +255,19 @@ describe("Marketplace curated scene routes", () => {
|
|
|
232
255
|
|
|
233
256
|
expect(screen.getByTestId("marketplace-loading-more")).toBeTruthy();
|
|
234
257
|
});
|
|
258
|
+
|
|
259
|
+
it("keeps the shelf layout stable while scenes are still loading", () => {
|
|
260
|
+
mocks.scenesQuery = createScenesQuery({
|
|
261
|
+
data: undefined,
|
|
262
|
+
isLoading: true,
|
|
263
|
+
isFetching: true,
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
render(<MarketplacePage forcedType="skills" />);
|
|
267
|
+
|
|
268
|
+
expect(screen.getByTestId("marketplace-scenes-skeleton")).toBeTruthy();
|
|
269
|
+
expect(screen.getByText("Recently updated")).toBeTruthy();
|
|
270
|
+
expect(screen.getByText("All Skills")).toBeTruthy();
|
|
271
|
+
expect(screen.queryByRole("button", { name: /Development/ })).toBeNull();
|
|
272
|
+
});
|
|
235
273
|
});
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
type MarketplaceShelfLocalizedText,
|
|
24
24
|
type MarketplaceShelfSceneVisual,
|
|
25
25
|
} from "@/features/marketplace/components/curated-shelves/marketplace-curated-shelves.config";
|
|
26
|
+
import { Skeleton } from "@/shared/components/ui/skeleton";
|
|
26
27
|
|
|
27
28
|
const SCENE_CARD_GRID_CLASS =
|
|
28
29
|
"grid grid-cols-[repeat(auto-fill,minmax(240px,320px))] justify-start gap-3";
|
|
@@ -36,6 +37,8 @@ export type MarketplaceShelfEntry = {
|
|
|
36
37
|
export function MarketplaceCuratedShelves(props: {
|
|
37
38
|
entries: MarketplaceShelfEntry[];
|
|
38
39
|
scenes: MarketplaceSceneView[];
|
|
40
|
+
isScenesLoading: boolean;
|
|
41
|
+
isItemsLoading: boolean;
|
|
39
42
|
language: string;
|
|
40
43
|
installState: InstallState;
|
|
41
44
|
onOpen: (entry: MarketplaceShelfEntry) => void;
|
|
@@ -45,6 +48,8 @@ export function MarketplaceCuratedShelves(props: {
|
|
|
45
48
|
const {
|
|
46
49
|
entries,
|
|
47
50
|
scenes,
|
|
51
|
+
isScenesLoading,
|
|
52
|
+
isItemsLoading,
|
|
48
53
|
language,
|
|
49
54
|
installState,
|
|
50
55
|
onOpen,
|
|
@@ -70,19 +75,23 @@ export function MarketplaceCuratedShelves(props: {
|
|
|
70
75
|
language,
|
|
71
76
|
)}
|
|
72
77
|
/>
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
78
|
+
{isScenesLoading ? (
|
|
79
|
+
<SceneGoalSkeletons />
|
|
80
|
+
) : (
|
|
81
|
+
<div className="grid grid-cols-2 gap-2 md:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5">
|
|
82
|
+
{scenes.map((scene) => (
|
|
83
|
+
<GoalCard
|
|
84
|
+
key={scene.scene}
|
|
85
|
+
scene={scene}
|
|
86
|
+
language={language}
|
|
87
|
+
onSelect={onOpenScene}
|
|
88
|
+
/>
|
|
89
|
+
))}
|
|
90
|
+
</div>
|
|
91
|
+
)}
|
|
83
92
|
</section>
|
|
84
93
|
|
|
85
|
-
{recentEntries.length > 0 && (
|
|
94
|
+
{(isItemsLoading || recentEntries.length > 0) && (
|
|
86
95
|
<ShelfItemRow
|
|
87
96
|
icon={Clock3}
|
|
88
97
|
title={readLocalized({ zh: "最近更新", en: "Recently updated" }, language)}
|
|
@@ -96,6 +105,7 @@ export function MarketplaceCuratedShelves(props: {
|
|
|
96
105
|
entries={recentEntries}
|
|
97
106
|
language={language}
|
|
98
107
|
localeFallbacks={localeFallbacks}
|
|
108
|
+
isLoading={isItemsLoading}
|
|
99
109
|
installState={installState}
|
|
100
110
|
onOpen={onOpen}
|
|
101
111
|
onInstall={onInstall}
|
|
@@ -105,6 +115,29 @@ export function MarketplaceCuratedShelves(props: {
|
|
|
105
115
|
);
|
|
106
116
|
}
|
|
107
117
|
|
|
118
|
+
function SceneGoalSkeletons() {
|
|
119
|
+
return (
|
|
120
|
+
<div
|
|
121
|
+
data-testid="marketplace-scenes-skeleton"
|
|
122
|
+
className="grid grid-cols-2 gap-2 md:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5"
|
|
123
|
+
>
|
|
124
|
+
{MARKETPLACE_SHELF_SCENE_VISUALS.map((scene) => (
|
|
125
|
+
<div
|
|
126
|
+
key={scene.scene}
|
|
127
|
+
className="flex min-h-[74px] flex-col justify-center rounded-lg border border-gray-200/70 bg-white px-3 py-2.5 shadow-sm"
|
|
128
|
+
>
|
|
129
|
+
<div className="flex min-w-0 items-center gap-2">
|
|
130
|
+
<Skeleton className="h-7 w-7 shrink-0 rounded-md" />
|
|
131
|
+
<Skeleton className="h-3.5 min-w-0 flex-1" />
|
|
132
|
+
<Skeleton className="h-3 w-8 shrink-0" />
|
|
133
|
+
</div>
|
|
134
|
+
<Skeleton className="mt-2 h-3 w-4/5" />
|
|
135
|
+
</div>
|
|
136
|
+
))}
|
|
137
|
+
</div>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
108
141
|
function GoalCard(props: {
|
|
109
142
|
scene: MarketplaceSceneView;
|
|
110
143
|
language: string;
|
|
@@ -259,6 +292,7 @@ function ShelfItemRow(props: {
|
|
|
259
292
|
entries: MarketplaceShelfEntry[];
|
|
260
293
|
language: string;
|
|
261
294
|
localeFallbacks: string[];
|
|
295
|
+
isLoading: boolean;
|
|
262
296
|
installState: InstallState;
|
|
263
297
|
onOpen: (entry: MarketplaceShelfEntry) => void;
|
|
264
298
|
onInstall: (item: MarketplaceItemSummary) => void;
|
|
@@ -270,6 +304,7 @@ function ShelfItemRow(props: {
|
|
|
270
304
|
entries,
|
|
271
305
|
language,
|
|
272
306
|
localeFallbacks,
|
|
307
|
+
isLoading,
|
|
273
308
|
installState,
|
|
274
309
|
onOpen,
|
|
275
310
|
onInstall,
|
|
@@ -278,23 +313,60 @@ function ShelfItemRow(props: {
|
|
|
278
313
|
return (
|
|
279
314
|
<section className="space-y-2.5">
|
|
280
315
|
<ShelfHeader icon={Icon} title={title} description={description} />
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
316
|
+
{isLoading ? (
|
|
317
|
+
<RecentShelfSkeletons />
|
|
318
|
+
) : (
|
|
319
|
+
<div className="-mx-1 flex gap-2.5 overflow-x-auto px-1 pb-1.5 custom-scrollbar">
|
|
320
|
+
{entries.map((entry) => (
|
|
321
|
+
<SkillShelfCard
|
|
322
|
+
key={entry.item.id}
|
|
323
|
+
entry={entry}
|
|
324
|
+
language={language}
|
|
325
|
+
localeFallbacks={localeFallbacks}
|
|
326
|
+
installState={installState}
|
|
327
|
+
onOpen={onOpen}
|
|
328
|
+
onInstall={onInstall}
|
|
329
|
+
/>
|
|
330
|
+
))}
|
|
331
|
+
</div>
|
|
332
|
+
)}
|
|
294
333
|
</section>
|
|
295
334
|
);
|
|
296
335
|
}
|
|
297
336
|
|
|
337
|
+
function RecentShelfSkeletons() {
|
|
338
|
+
return (
|
|
339
|
+
<div
|
|
340
|
+
data-testid="marketplace-recent-skeleton"
|
|
341
|
+
className="-mx-1 flex gap-2.5 overflow-hidden px-1 pb-1.5"
|
|
342
|
+
>
|
|
343
|
+
{Array.from({ length: 4 }, (_, index) => (
|
|
344
|
+
<article
|
|
345
|
+
key={`marketplace-recent-skeleton-${index}`}
|
|
346
|
+
className="flex min-h-[166px] w-[260px] shrink-0 flex-col justify-between rounded-xl border border-gray-200/70 bg-white p-3 shadow-sm"
|
|
347
|
+
>
|
|
348
|
+
<div>
|
|
349
|
+
<div className="mb-2.5 flex min-w-0 items-start gap-2.5">
|
|
350
|
+
<Skeleton className="h-10 w-10 shrink-0 rounded-xl" />
|
|
351
|
+
<div className="min-w-0 flex-1 space-y-1.5 pt-0.5">
|
|
352
|
+
<Skeleton className="h-3.5 w-32 max-w-full" />
|
|
353
|
+
<Skeleton className="h-3 w-24 max-w-full" />
|
|
354
|
+
</div>
|
|
355
|
+
</div>
|
|
356
|
+
<Skeleton className="h-3 w-full" />
|
|
357
|
+
<Skeleton className="mt-1.5 h-3 w-4/5" />
|
|
358
|
+
<Skeleton className="mt-2 h-3 w-24" />
|
|
359
|
+
</div>
|
|
360
|
+
<div className="mt-3 flex items-center justify-between gap-3 border-t border-gray-100 pt-2.5">
|
|
361
|
+
<Skeleton className="h-3 w-20" />
|
|
362
|
+
<Skeleton className="h-7 w-16 rounded-md" />
|
|
363
|
+
</div>
|
|
364
|
+
</article>
|
|
365
|
+
))}
|
|
366
|
+
</div>
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
|
|
298
370
|
function ShelfHeader({
|
|
299
371
|
icon: Icon,
|
|
300
372
|
title,
|
|
@@ -207,7 +207,11 @@ describe("MarketplacePage", () => {
|
|
|
207
207
|
|
|
208
208
|
const { container } = render(<MarketplacePage forcedType="skills" />);
|
|
209
209
|
|
|
210
|
+
expect(screen.getByText("Recently updated")).toBeTruthy();
|
|
211
|
+
expect(screen.getByTestId("marketplace-recent-skeleton")).toBeTruthy();
|
|
212
|
+
expect(screen.getByText("All Skills")).toBeTruthy();
|
|
210
213
|
expect(screen.getByTestId("marketplace-list-skeleton")).toBeTruthy();
|
|
214
|
+
expect(screen.queryByText("Skill Catalog")).toBeNull();
|
|
211
215
|
expect(
|
|
212
216
|
container.querySelectorAll(
|
|
213
217
|
'[data-testid="marketplace-list-skeleton"] > article',
|
|
@@ -504,16 +504,18 @@ export function MarketplacePage(props: MarketplacePageProps = {}) {
|
|
|
504
504
|
)}
|
|
505
505
|
|
|
506
506
|
<section className="flex min-h-0 flex-1 flex-col">
|
|
507
|
-
{!curatedSceneRoute.isSceneRoute &&
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
507
|
+
{!curatedSceneRoute.isSceneRoute &&
|
|
508
|
+
!curatedSceneRoute.showShelves &&
|
|
509
|
+
!showListSkeleton && (
|
|
510
|
+
<div className="mb-3 flex items-center justify-between">
|
|
511
|
+
<h3 className="text-[14px] font-semibold text-gray-900">
|
|
512
|
+
{scope === "installed"
|
|
513
|
+
? t(copyKeys.sectionInstalled)
|
|
514
|
+
: language.startsWith("zh") ? "全部技能" : "All Skills"}
|
|
515
|
+
</h3>
|
|
516
|
+
<span className="text-[12px] text-gray-500">{listSummary}</span>
|
|
517
|
+
</div>
|
|
518
|
+
)}
|
|
517
519
|
|
|
518
520
|
{scope === "all" && itemsQuery.isError && (
|
|
519
521
|
<div className="rounded-xl border border-rose-200 bg-rose-50 p-4 text-sm text-rose-700">
|
|
@@ -549,6 +551,8 @@ export function MarketplacePage(props: MarketplacePageProps = {}) {
|
|
|
549
551
|
<MarketplaceCuratedShelves
|
|
550
552
|
entries={curatedSceneRoute.entries}
|
|
551
553
|
scenes={curatedSceneRoute.scenes}
|
|
554
|
+
isScenesLoading={curatedSceneRoute.isScenesLoading}
|
|
555
|
+
isItemsLoading={showListSkeleton}
|
|
552
556
|
language={language}
|
|
553
557
|
installState={installState}
|
|
554
558
|
onOpen={(entry) => void openItemDetail(entry.item, entry.record)}
|
|
@@ -565,7 +569,7 @@ export function MarketplacePage(props: MarketplacePageProps = {}) {
|
|
|
565
569
|
title={
|
|
566
570
|
scope === "installed"
|
|
567
571
|
? t(copyKeys.sectionInstalled)
|
|
568
|
-
:
|
|
572
|
+
: language.startsWith("zh") ? "全部技能" : "All Skills"
|
|
569
573
|
}
|
|
570
574
|
summary={listSummary}
|
|
571
575
|
showTitle={curatedSceneRoute.showShelves}
|
|
@@ -603,7 +607,7 @@ export function MarketplacePage(props: MarketplacePageProps = {}) {
|
|
|
603
607
|
)}
|
|
604
608
|
|
|
605
609
|
{scope === "all" &&
|
|
606
|
-
!
|
|
610
|
+
!showListSkeleton &&
|
|
607
611
|
!itemsQuery.isError && (
|
|
608
612
|
<MarketplaceInfiniteScrollStatus
|
|
609
613
|
hasMore={Boolean(itemsQuery.hasNextPage)}
|
|
@@ -73,16 +73,24 @@ export function useMarketplaceCuratedSceneRoute(
|
|
|
73
73
|
};
|
|
74
74
|
}, [scene, scenes]);
|
|
75
75
|
const isSceneRoute = typeFilter === "skill" && Boolean(scene?.trim());
|
|
76
|
-
const
|
|
76
|
+
const isShelfHome =
|
|
77
77
|
typeFilter === "skill" &&
|
|
78
78
|
scope === "all" &&
|
|
79
79
|
!searchText.trim() &&
|
|
80
80
|
!query &&
|
|
81
|
-
!showListSkeleton &&
|
|
82
|
-
!hasCatalogError &&
|
|
83
|
-
scenes.length > 0 &&
|
|
84
|
-
items.length >= 4 &&
|
|
85
81
|
!isSceneRoute;
|
|
82
|
+
const hasShelfCatalog = showListSkeleton || items.length >= 4;
|
|
83
|
+
const isScenesLoading =
|
|
84
|
+
isShelfHome &&
|
|
85
|
+
!hasCatalogError &&
|
|
86
|
+
hasShelfCatalog &&
|
|
87
|
+
scenes.length === 0 &&
|
|
88
|
+
scenesQuery.isLoading;
|
|
89
|
+
const showShelves =
|
|
90
|
+
isShelfHome &&
|
|
91
|
+
!hasCatalogError &&
|
|
92
|
+
hasShelfCatalog &&
|
|
93
|
+
(showListSkeleton || scenes.length > 0 || isScenesLoading);
|
|
86
94
|
|
|
87
95
|
return {
|
|
88
96
|
entries,
|
|
@@ -90,6 +98,7 @@ export function useMarketplaceCuratedSceneRoute(
|
|
|
90
98
|
selectedScene,
|
|
91
99
|
sceneEntries: entries,
|
|
92
100
|
isSceneRoute,
|
|
101
|
+
isScenesLoading,
|
|
93
102
|
showShelves,
|
|
94
103
|
backPath: forcedType ? "/skills" : "/marketplace/skills",
|
|
95
104
|
pathPrefix: forcedType ? "/skills/scenes" : "/marketplace/skills/scenes",
|