@linxin666/dsh-remote-web-ui 0.3.6 → 0.3.9
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/README.i18n.yaml +2 -2
- package/README.md +77 -468
- package/README.zh.md +83 -113
- package/lib/client.js +955 -203
- package/lib/client.js.map +1 -1
- package/lib/index.js +1136 -1466
- package/lib/types/client/FooterRemoteEntry.d.ts +6 -7
- package/lib/types/client/FooterRemoteEntry.d.ts.map +1 -1
- package/lib/types/client/FooterRemoteEntry.js +1 -1
- package/lib/types/client/RemoteEntry.d.ts +7 -4
- package/lib/types/client/RemoteEntry.d.ts.map +1 -1
- package/lib/types/client/RemoteEntry.js +7 -10
- package/lib/types/client/RemotePanel.d.ts +2 -2
- package/lib/types/client/RemotePanel.d.ts.map +1 -1
- package/lib/types/client/RemotePanel.js +2 -2
- package/lib/types/client/RemoteSettingsCard.d.ts +9 -5
- package/lib/types/client/RemoteSettingsCard.d.ts.map +1 -1
- package/lib/types/client/RemoteSettingsCard.js +56 -3
- package/lib/types/client/deep-link.d.ts +10 -13
- package/lib/types/client/deep-link.d.ts.map +1 -1
- package/lib/types/client/deep-link.js +14 -74
- package/lib/types/client/index.d.ts +2 -6
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +73 -39
- package/lib/types/client/locales.d.ts +26 -12
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +36 -22
- package/lib/types/client/mobile-adapt.d.ts +40 -0
- package/lib/types/client/mobile-adapt.d.ts.map +1 -0
- package/lib/types/client/mobile-adapt.js +967 -0
- package/lib/types/client/pair-api.d.ts +24 -6
- package/lib/types/client/pair-api.d.ts.map +1 -1
- package/lib/types/client/pair-api.js +9 -14
- package/lib/types/client/remote-channel.d.ts +3 -0
- package/lib/types/client/remote-channel.d.ts.map +1 -1
- package/lib/types/client/remote-channel.js +35 -3
- package/lib/types/client/settings-form.d.ts +35 -43
- package/lib/types/client/settings-form.d.ts.map +1 -1
- package/lib/types/client/settings-form.js +60 -53
- package/lib/types/firewall.d.ts +75 -0
- package/lib/types/firewall.d.ts.map +1 -0
- package/lib/types/firewall.js +184 -0
- package/lib/types/gate.d.ts +11 -5
- package/lib/types/gate.d.ts.map +1 -1
- package/lib/types/gate.js +11 -5
- package/lib/types/index.d.ts +39 -10
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +206 -58
- package/lib/types/inner-auth.d.ts +32 -0
- package/lib/types/inner-auth.d.ts.map +1 -0
- package/lib/types/inner-auth.js +72 -0
- package/lib/types/lan-bind-plan.d.ts +55 -0
- package/lib/types/lan-bind-plan.d.ts.map +1 -0
- package/lib/types/lan-bind-plan.js +60 -0
- package/lib/types/lan-bind.d.ts +69 -0
- package/lib/types/lan-bind.d.ts.map +1 -0
- package/lib/types/lan-bind.js +128 -0
- package/lib/types/loopback-proxy.d.ts +9 -2
- package/lib/types/loopback-proxy.d.ts.map +1 -1
- package/lib/types/loopback-proxy.js +24 -2
- package/lib/types/pairing.d.ts +15 -3
- package/lib/types/pairing.d.ts.map +1 -1
- package/lib/types/pairing.js +20 -6
- package/lib/types/remote-api.d.ts +30 -13
- package/lib/types/remote-api.d.ts.map +1 -1
- package/lib/types/remote-api.js +55 -42
- package/lib/types/remote-channel-boot.d.ts +21 -2
- package/lib/types/remote-channel-boot.d.ts.map +1 -1
- package/lib/types/remote-channel-boot.js +65 -3
- package/lib/types/remote-channel-rules.d.ts +8 -0
- package/lib/types/remote-channel-rules.d.ts.map +1 -1
- package/lib/types/remote-channel-rules.js +9 -2
- package/lib/types/remote-methods.d.ts +45 -11
- package/lib/types/remote-methods.d.ts.map +1 -1
- package/lib/types/remote-methods.js +56 -27
- package/lib/types/remote-presence-pet.d.ts +51 -0
- package/lib/types/remote-presence-pet.d.ts.map +1 -0
- package/lib/types/remote-presence-pet.js +101 -0
- package/lib/types/routes.d.ts +76 -4
- package/lib/types/routes.d.ts.map +1 -1
- package/lib/types/routes.js +336 -19
- package/lib/types/tunnel.d.ts.map +1 -1
- package/lib/types/tunnel.js +7 -1
- package/package.json +20 -18
- package/src/client/FooterRemoteEntry.tsx +9 -10
- package/src/client/RemoteEntry.tsx +13 -14
- package/src/client/RemotePanel.tsx +5 -16
- package/src/client/RemoteSettingsCard.tsx +72 -13
- package/src/client/deep-link.ts +14 -75
- package/src/client/index.ts +77 -39
- package/src/client/locales.ts +36 -22
- package/src/client/mobile-adapt.ts +913 -0
- package/src/client/pair-api.ts +26 -15
- package/src/client/remote-channel.ts +31 -3
- package/src/client/settings-form.ts +78 -92
- package/src/dsh-home.ts +1 -1
- package/src/firewall.ts +230 -0
- package/src/gate.ts +11 -5
- package/src/index.ts +235 -65
- package/src/inner-auth.ts +85 -0
- package/src/lan-bind-plan.ts +81 -0
- package/src/lan-bind.ts +145 -0
- package/src/loopback-proxy.ts +34 -0
- package/src/pairing.persist.test.ts +1 -1
- package/src/pairing.ts +20 -6
- package/src/remote-api.ts +67 -45
- package/src/remote-channel-boot.ts +70 -3
- package/src/remote-channel-rules.ts +17 -2
- package/src/remote-methods.ts +58 -27
- package/src/remote-presence-pet.ts +129 -0
- package/src/routes.ts +358 -19
- package/src/tunnel.ts +7 -1
- package/assets/apple-touch-icon.png +0 -0
- package/assets/mobile-icon-192.png +0 -0
- package/assets/mobile-icon-512.png +0 -0
- package/assets/mobile-service-worker.js +0 -63
- package/lib/mobile.js +0 -17658
- package/lib/mobile.js.map +0 -1
- package/lib/types/mobile/PairRequiredView.d.ts +0 -7
- package/lib/types/mobile/PairRequiredView.d.ts.map +0 -1
- package/lib/types/mobile/PairRequiredView.js +0 -28
- package/lib/types/mobile/PairRequiredView.test.d.ts +0 -2
- package/lib/types/mobile/PairRequiredView.test.d.ts.map +0 -1
- package/lib/types/mobile/PairRequiredView.test.js +0 -24
- package/lib/types/mobile/api.d.ts +0 -145
- package/lib/types/mobile/api.d.ts.map +0 -1
- package/lib/types/mobile/api.js +0 -103
- package/lib/types/mobile/index.d.ts +0 -10
- package/lib/types/mobile/index.d.ts.map +0 -1
- package/lib/types/mobile/index.js +0 -38
- package/lib/types/mobile/markdown.d.ts +0 -34
- package/lib/types/mobile/markdown.d.ts.map +0 -1
- package/lib/types/mobile/markdown.js +0 -285
- package/lib/types/mobile/markdown.test.d.ts +0 -2
- package/lib/types/mobile/markdown.test.d.ts.map +0 -1
- package/lib/types/mobile/markdown.test.js +0 -79
- package/lib/types/mobile/messages.d.ts +0 -149
- package/lib/types/mobile/messages.d.ts.map +0 -1
- package/lib/types/mobile/messages.js +0 -561
- package/lib/types/mobile/messages.test.d.ts +0 -2
- package/lib/types/mobile/messages.test.d.ts.map +0 -1
- package/lib/types/mobile/messages.test.js +0 -356
- package/lib/types/mobile/mobile-styles.d.ts +0 -6
- package/lib/types/mobile/mobile-styles.d.ts.map +0 -1
- package/lib/types/mobile/mobile-styles.js +0 -1438
- package/lib/types/mobile/mobile-theme.d.ts +0 -22
- package/lib/types/mobile/mobile-theme.d.ts.map +0 -1
- package/lib/types/mobile/mobile-theme.js +0 -70
- package/lib/types/mobile/mobile-theme.test.d.ts +0 -2
- package/lib/types/mobile/mobile-theme.test.d.ts.map +0 -1
- package/lib/types/mobile/mobile-theme.test.js +0 -71
- package/lib/types/mobile/mux.d.ts +0 -128
- package/lib/types/mobile/mux.d.ts.map +0 -1
- package/lib/types/mobile/mux.js +0 -305
- package/lib/types/mobile/mux.test.d.ts +0 -2
- package/lib/types/mobile/mux.test.d.ts.map +0 -1
- package/lib/types/mobile/mux.test.js +0 -267
- package/lib/types/mobile/pairing.d.ts +0 -31
- package/lib/types/mobile/pairing.d.ts.map +0 -1
- package/lib/types/mobile/pairing.js +0 -62
- package/lib/types/mobile/rpc.d.ts +0 -38
- package/lib/types/mobile/rpc.d.ts.map +0 -1
- package/lib/types/mobile/rpc.js +0 -83
- package/lib/types/mobile/theme-toggle.d.ts +0 -12
- package/lib/types/mobile/theme-toggle.d.ts.map +0 -1
- package/lib/types/mobile/theme-toggle.js +0 -26
- package/lib/types/mobile/views/App.d.ts +0 -57
- package/lib/types/mobile/views/App.d.ts.map +0 -1
- package/lib/types/mobile/views/App.js +0 -150
- package/lib/types/mobile/views/App.test.d.ts +0 -2
- package/lib/types/mobile/views/App.test.d.ts.map +0 -1
- package/lib/types/mobile/views/App.test.js +0 -52
- package/lib/types/mobile/views/ChatView.d.ts +0 -63
- package/lib/types/mobile/views/ChatView.d.ts.map +0 -1
- package/lib/types/mobile/views/ChatView.js +0 -821
- package/lib/types/mobile/views/ChatView.stream.test.d.ts +0 -2
- package/lib/types/mobile/views/ChatView.stream.test.d.ts.map +0 -1
- package/lib/types/mobile/views/ChatView.stream.test.js +0 -236
- package/lib/types/mobile/views/ChatView.test.d.ts +0 -2
- package/lib/types/mobile/views/ChatView.test.d.ts.map +0 -1
- package/lib/types/mobile/views/ChatView.test.js +0 -718
- package/lib/types/mobile/views/SessionListView.d.ts +0 -27
- package/lib/types/mobile/views/SessionListView.d.ts.map +0 -1
- package/lib/types/mobile/views/SessionListView.js +0 -139
- package/lib/types/mobile/views/SessionListView.test.d.ts +0 -2
- package/lib/types/mobile/views/SessionListView.test.d.ts.map +0 -1
- package/lib/types/mobile/views/SessionListView.test.js +0 -167
- package/lib/types/mobile/views/WorkspaceView.d.ts +0 -21
- package/lib/types/mobile/views/WorkspaceView.d.ts.map +0 -1
- package/lib/types/mobile/views/WorkspaceView.js +0 -93
- package/lib/types/mobile/views/WorkspaceView.test.d.ts +0 -2
- package/lib/types/mobile/views/WorkspaceView.test.d.ts.map +0 -1
- package/lib/types/mobile/views/WorkspaceView.test.js +0 -183
- package/lib/types/mobile-api.d.ts +0 -65
- package/lib/types/mobile-api.d.ts.map +0 -1
- package/lib/types/mobile-api.js +0 -404
- package/lib/types/mobile-pending.d.ts +0 -38
- package/lib/types/mobile-pending.d.ts.map +0 -1
- package/lib/types/mobile-pending.js +0 -59
- package/lib/types/mobile-pwa.d.ts +0 -6
- package/lib/types/mobile-pwa.d.ts.map +0 -1
- package/lib/types/mobile-pwa.js +0 -17
- package/lib/types/mobile-routes.d.ts +0 -13
- package/lib/types/mobile-routes.d.ts.map +0 -1
- package/lib/types/mobile-routes.js +0 -217
- package/lib/types/paired-model-catalog.d.ts +0 -17
- package/lib/types/paired-model-catalog.d.ts.map +0 -1
- package/lib/types/paired-model-catalog.js +0 -311
- package/src/mobile/PairRequiredView.test.tsx +0 -28
- package/src/mobile/PairRequiredView.tsx +0 -58
- package/src/mobile/api.ts +0 -229
- package/src/mobile/index.tsx +0 -41
- package/src/mobile/markdown.test.ts +0 -91
- package/src/mobile/markdown.ts +0 -291
- package/src/mobile/messages.test.ts +0 -402
- package/src/mobile/messages.ts +0 -685
- package/src/mobile/mobile-styles.ts +0 -1439
- package/src/mobile/mobile-theme.test.ts +0 -84
- package/src/mobile/mobile-theme.ts +0 -76
- package/src/mobile/mux.test.ts +0 -314
- package/src/mobile/mux.ts +0 -336
- package/src/mobile/pairing.ts +0 -77
- package/src/mobile/rpc.ts +0 -90
- package/src/mobile/theme-toggle.tsx +0 -47
- package/src/mobile/views/App.test.tsx +0 -62
- package/src/mobile/views/App.tsx +0 -213
- package/src/mobile/views/ChatView.stream.test.tsx +0 -268
- package/src/mobile/views/ChatView.test.tsx +0 -816
- package/src/mobile/views/ChatView.tsx +0 -1342
- package/src/mobile/views/SessionListView.test.tsx +0 -207
- package/src/mobile/views/SessionListView.tsx +0 -244
- package/src/mobile/views/WorkspaceView.test.tsx +0 -227
- package/src/mobile/views/WorkspaceView.tsx +0 -245
- package/src/mobile-api.ts +0 -418
- package/src/mobile-pending.ts +0 -88
- package/src/mobile-pwa.ts +0 -19
- package/src/mobile-routes.ts +0 -231
- package/src/paired-model-catalog.ts +0 -272
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sessions level: one workspace's sessions, loaded incrementally — the
|
|
3
|
-
* first page fetches session.list with a cursor, scrolling appends further
|
|
4
|
-
* pages (never the whole list at once). Rows are filtered to the opened
|
|
5
|
-
* workspace by its owned session ids; extra pages are pulled on demand so
|
|
6
|
-
* a workspace with many sessions converges without a full transfer.
|
|
7
|
-
*
|
|
8
|
-
* Creating a session is this level's other action: the workspace's id is
|
|
9
|
-
* sent to session.create (the host attaches the new session to it), the
|
|
10
|
-
* fresh row is prepended optimistically, and the user lands straight in
|
|
11
|
-
* the new chat — the same "new session opens" flow as the desktop UI.
|
|
12
|
-
*/
|
|
13
|
-
import type { WorkspaceView as WorkspaceRow } from '@deepseek-ai/dsh-host-apiproxy/api/workspace';
|
|
14
|
-
import { type SessionView } from './App.tsx';
|
|
15
|
-
/** Props for the session list. */
|
|
16
|
-
export interface SessionListViewProps {
|
|
17
|
-
workspace: WorkspaceRow;
|
|
18
|
-
onBack(): void;
|
|
19
|
-
onPick(session: SessionView): void;
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Render one workspace's paged session list.
|
|
23
|
-
* @param props - the workspace, back action, and pick action.
|
|
24
|
-
* @returns the session list.
|
|
25
|
-
*/
|
|
26
|
-
export declare function SessionListView({ workspace, onBack, onPick }: SessionListViewProps): import("react").JSX.Element;
|
|
27
|
-
//# sourceMappingURL=SessionListView.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"SessionListView.d.ts","sourceRoot":"","sources":["../../../../src/mobile/views/SessionListView.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,aAAa,IAAI,YAAY,EAAE,MAAM,8CAA8C,CAAA;AAIjG,OAAO,EAAuD,KAAK,WAAW,EAAE,MAAM,WAAW,CAAA;AAGjG,kCAAkC;AAClC,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,YAAY,CAAA;IACvB,MAAM,IAAI,IAAI,CAAA;IACd,MAAM,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAAA;CACnC;AAUD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,oBAAoB,+BA0MlF"}
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
/**
|
|
3
|
-
* Sessions level: one workspace's sessions, loaded incrementally — the
|
|
4
|
-
* first page fetches session.list with a cursor, scrolling appends further
|
|
5
|
-
* pages (never the whole list at once). Rows are filtered to the opened
|
|
6
|
-
* workspace by its owned session ids; extra pages are pulled on demand so
|
|
7
|
-
* a workspace with many sessions converges without a full transfer.
|
|
8
|
-
*
|
|
9
|
-
* Creating a session is this level's other action: the workspace's id is
|
|
10
|
-
* sent to session.create (the host attaches the new session to it), the
|
|
11
|
-
* fresh row is prepended optimistically, and the user lands straight in
|
|
12
|
-
* the new chat — the same "new session opens" flow as the desktop UI.
|
|
13
|
-
*/
|
|
14
|
-
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
15
|
-
import { createSession, listAgentPresets, listSessions, listWorkspaces } from "../api.js";
|
|
16
|
-
import { errorText, formatTime, staleHostHint, toSessionView } from "./App.js";
|
|
17
|
-
import { ThemeToggle } from "../theme-toggle.js";
|
|
18
|
-
/** Rows that belong to the opened workspace (its owned session id set). */
|
|
19
|
-
function ownedItems(page, workspace) {
|
|
20
|
-
const owned = new Set(workspace.sessionIds);
|
|
21
|
-
return page
|
|
22
|
-
.filter(item => owned.has(item.sessionId))
|
|
23
|
-
.map(item => toSessionView(item));
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Render one workspace's paged session list.
|
|
27
|
-
* @param props - the workspace, back action, and pick action.
|
|
28
|
-
* @returns the session list.
|
|
29
|
-
*/
|
|
30
|
-
export function SessionListView({ workspace, onBack, onPick }) {
|
|
31
|
-
const [rows, setRows] = useState([]);
|
|
32
|
-
const [hasMore, setHasMore] = useState(false);
|
|
33
|
-
const [loading, setLoading] = useState(true);
|
|
34
|
-
const [error, setError] = useState(undefined);
|
|
35
|
-
const [creating, setCreating] = useState(false);
|
|
36
|
-
const [createError, setCreateError] = useState(undefined);
|
|
37
|
-
const [presets, setPresets] = useState([]);
|
|
38
|
-
const [selectedPreset, setSelectedPreset] = useState(undefined);
|
|
39
|
-
const [presetsLoading, setPresetsLoading] = useState(true);
|
|
40
|
-
const cursorRef = useRef(undefined);
|
|
41
|
-
const busyRef = useRef(false);
|
|
42
|
-
// The freshest owned-id set: the mount effect re-reads the workspace roster
|
|
43
|
-
// so a session created since this row was captured still shows; loadMore
|
|
44
|
-
// filters later pages through this ref instead of the stale prop.
|
|
45
|
-
const workspaceRef = useRef(workspace);
|
|
46
|
-
// First page on mount. The workspace roster is re-read alongside so the
|
|
47
|
-
// owned-id set is fresh: a session created (or attached) since this
|
|
48
|
-
// workspace row was captured must not vanish from the filter.
|
|
49
|
-
useEffect(() => {
|
|
50
|
-
let cancelled = false;
|
|
51
|
-
setLoading(true);
|
|
52
|
-
setError(undefined);
|
|
53
|
-
void Promise.all([listSessions(), listWorkspaces()]).then(([page, workspaces]) => {
|
|
54
|
-
if (cancelled)
|
|
55
|
-
return;
|
|
56
|
-
const fresh = workspaces.find(candidate => candidate.workspaceId === workspace.workspaceId);
|
|
57
|
-
const current = fresh ?? workspace;
|
|
58
|
-
workspaceRef.current = current;
|
|
59
|
-
setRows(ownedItems(page.items, current));
|
|
60
|
-
cursorRef.current = page.nextCursor;
|
|
61
|
-
setHasMore(page.hasMore);
|
|
62
|
-
setLoading(false);
|
|
63
|
-
}, (reason) => {
|
|
64
|
-
if (cancelled)
|
|
65
|
-
return;
|
|
66
|
-
setError(errorText(reason));
|
|
67
|
-
setLoading(false);
|
|
68
|
-
});
|
|
69
|
-
return () => { cancelled = true; };
|
|
70
|
-
}, [workspace]);
|
|
71
|
-
useEffect(() => {
|
|
72
|
-
let cancelled = false;
|
|
73
|
-
setPresets([]);
|
|
74
|
-
setSelectedPreset(undefined);
|
|
75
|
-
setPresetsLoading(true);
|
|
76
|
-
void listAgentPresets().then((roster) => {
|
|
77
|
-
if (cancelled)
|
|
78
|
-
return;
|
|
79
|
-
const usable = roster.presets.filter(preset => preset.broken === undefined);
|
|
80
|
-
setPresets(roster.presets);
|
|
81
|
-
setSelectedPreset((usable.find(preset => preset.isDefault) ?? usable[0])?.id);
|
|
82
|
-
setPresetsLoading(false);
|
|
83
|
-
}, () => {
|
|
84
|
-
if (cancelled)
|
|
85
|
-
return;
|
|
86
|
-
setPresetsLoading(false);
|
|
87
|
-
});
|
|
88
|
-
return () => { cancelled = true; };
|
|
89
|
-
}, [workspace]);
|
|
90
|
-
/** Pull the next page and append the rows that belong to this workspace. */
|
|
91
|
-
const loadMore = useCallback(() => {
|
|
92
|
-
if (busyRef.current)
|
|
93
|
-
return;
|
|
94
|
-
const cursor = cursorRef.current;
|
|
95
|
-
if (cursor === undefined)
|
|
96
|
-
return;
|
|
97
|
-
busyRef.current = true;
|
|
98
|
-
setLoading(true);
|
|
99
|
-
void listSessions(cursor).then((page) => {
|
|
100
|
-
busyRef.current = false;
|
|
101
|
-
setLoading(false);
|
|
102
|
-
cursorRef.current = page.nextCursor;
|
|
103
|
-
setHasMore(page.hasMore);
|
|
104
|
-
setRows(previous => [...previous, ...ownedItems(page.items, workspaceRef.current)]);
|
|
105
|
-
}, (reason) => {
|
|
106
|
-
busyRef.current = false;
|
|
107
|
-
setLoading(false);
|
|
108
|
-
setError(errorText(reason));
|
|
109
|
-
});
|
|
110
|
-
}, [workspace]);
|
|
111
|
-
/** Create a blank session in this workspace and open it immediately. */
|
|
112
|
-
const handleCreate = useCallback(() => {
|
|
113
|
-
if (creating)
|
|
114
|
-
return;
|
|
115
|
-
setCreating(true);
|
|
116
|
-
setCreateError(undefined);
|
|
117
|
-
void createSession({
|
|
118
|
-
workspaceId: workspace.workspaceId,
|
|
119
|
-
...(selectedPreset !== undefined ? { agentPreset: selectedPreset } : {}),
|
|
120
|
-
}).then((created) => {
|
|
121
|
-
setCreating(false);
|
|
122
|
-
const view = {
|
|
123
|
-
sessionId: created.sessionId,
|
|
124
|
-
title: '新会话',
|
|
125
|
-
updatedAt: Date.now(),
|
|
126
|
-
running: false,
|
|
127
|
-
blank: true,
|
|
128
|
-
};
|
|
129
|
-
setRows(previous => [view, ...previous]);
|
|
130
|
-
onPick(view);
|
|
131
|
-
}, (reason) => {
|
|
132
|
-
setCreating(false);
|
|
133
|
-
setCreateError(errorText(reason));
|
|
134
|
-
});
|
|
135
|
-
}, [creating, workspace, onPick, selectedPreset]);
|
|
136
|
-
const createHint = createError !== undefined ? staleHostHint(createError) : undefined;
|
|
137
|
-
const selectedPresetEntry = presets.find(preset => preset.id === selectedPreset);
|
|
138
|
-
return (_jsxs("div", { className: "mobile", children: [_jsxs("header", { className: "mobile-header", children: [_jsx("button", { type: "button", className: "mobile-back", "aria-label": "\u8FD4\u56DE", onClick: onBack, children: "\u2039" }), _jsx("h1", { className: "mobile-title mobile-titleInline", children: workspace.title }), _jsx(ThemeToggle, {})] }), error !== undefined && _jsx("p", { className: "mobile-error mobile-pad", children: error }), _jsxs("div", { className: "mobile-create mobile-pad", children: [presets.length > 0 && (_jsxs("label", { className: "mobile-preset", children: [_jsx("span", { className: "mobile-presetLabel", children: "Agent \u6A21\u5F0F" }), _jsxs("select", { className: "mobile-presetSelect", value: selectedPreset ?? '', disabled: selectedPreset === undefined, onChange: (event) => { setSelectedPreset(event.target.value); }, children: [selectedPreset === undefined && _jsx("option", { value: "", children: "\u65E0\u53EF\u7528 Agent \u6A21\u5F0F" }), presets.map(preset => (_jsxs("option", { value: preset.id, disabled: preset.broken !== undefined, children: [preset.name ?? preset.id, preset.isDefault ? '(默认)' : '', preset.trust === 'user' ? '(本地)' : '', preset.broken !== undefined ? '(不可用)' : ''] }, preset.id)))] })] })), selectedPresetEntry?.description !== undefined && (_jsx("p", { className: "mobile-presetDescription", children: selectedPresetEntry.description })), _jsx("button", { type: "button", className: "mobile-new", disabled: creating || presetsLoading, onClick: () => { void handleCreate(); }, children: creating ? '创建中…' : '+ 新建会话' })] }), createError !== undefined && (_jsxs("p", { className: "mobile-error mobile-pad", children: [createError, createHint !== undefined && _jsx("span", { className: "mobile-hint", children: createHint })] })), _jsx("ul", { className: "mobile-list", children: rows.map(row => (_jsx("li", { children: _jsxs("button", { type: "button", className: "mobile-row", onClick: () => { onPick(row); }, children: [_jsxs("span", { className: "mobile-rowMain", children: [_jsxs("span", { className: "mobile-rowTitle", children: [row.blank ? '新会话' : row.title, row.running ? _jsx("span", { className: "mobile-live", children: "\u25CF" }) : null] }), _jsx("span", { className: "mobile-rowMeta", children: formatTime(row.updatedAt) })] }), _jsx("span", { className: "mobile-chevron", children: "\u203A" })] }) }, row.sessionId))) }), hasMore && (_jsx("div", { className: "mobile-pad", children: _jsx("button", { type: "button", className: "mobile-button mobile-block", disabled: loading, onClick: () => { void loadMore(); }, children: loading ? '加载中…' : '加载更多会话' }) })), !hasMore && rows.length === 0 && !loading && (_jsx("div", { className: "mobile-empty", children: _jsx("p", { className: "mobile-muted", children: "\u8BE5\u5DE5\u4F5C\u533A\u8FD8\u6CA1\u6709\u4F1A\u8BDD\uFF0C\u70B9\u4E0A\u65B9\u6309\u94AE\u65B0\u5EFA\u4E00\u4E2A" }) }))] }));
|
|
139
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"SessionListView.test.d.ts","sourceRoot":"","sources":["../../../../src/mobile/views/SessionListView.test.tsx"],"names":[],"mappings":""}
|
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
// @vitest-environment jsdom
|
|
3
|
-
/** SessionListView: owned-row filtering, incremental pages, session creation. */
|
|
4
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
-
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
6
|
-
import { SessionListView } from "./SessionListView.js";
|
|
7
|
-
// The api module is fully mocked; the view's App.tsx helpers stay real.
|
|
8
|
-
vi.mock('../api.ts', () => ({
|
|
9
|
-
listSessions: vi.fn(),
|
|
10
|
-
listWorkspaces: vi.fn(),
|
|
11
|
-
listAgentPresets: vi.fn(),
|
|
12
|
-
createSession: vi.fn(),
|
|
13
|
-
}));
|
|
14
|
-
import { createSession, listAgentPresets, listSessions, listWorkspaces } from "../api.js";
|
|
15
|
-
const listSessionsMock = vi.mocked(listSessions);
|
|
16
|
-
const listWorkspacesMock = vi.mocked(listWorkspaces);
|
|
17
|
-
const listAgentPresetsMock = vi.mocked(listAgentPresets);
|
|
18
|
-
const createSessionMock = vi.mocked(createSession);
|
|
19
|
-
const workspace = {
|
|
20
|
-
workspaceId: 'w-1',
|
|
21
|
-
path: '/tmp/demo',
|
|
22
|
-
title: '演示项目',
|
|
23
|
-
sessionIds: ['s-1'],
|
|
24
|
-
createdAt: '2026-01-01T00:00:00.000Z',
|
|
25
|
-
updatedAt: '2026-01-01T00:00:00.000Z',
|
|
26
|
-
};
|
|
27
|
-
/** A session.list summary row. */
|
|
28
|
-
function summary(sessionId, updatedAt, extra = {}) {
|
|
29
|
-
return { sessionId, updatedAt, running: false, blank: false, ...extra };
|
|
30
|
-
}
|
|
31
|
-
let picked;
|
|
32
|
-
function renderList(props = {}) {
|
|
33
|
-
picked = undefined;
|
|
34
|
-
render(_jsx(SessionListView, { workspace: workspace, onBack: () => { }, onPick: (session) => { picked = session; }, ...props }));
|
|
35
|
-
}
|
|
36
|
-
beforeEach(() => {
|
|
37
|
-
listSessionsMock.mockResolvedValue({ items: [], hasMore: false });
|
|
38
|
-
listWorkspacesMock.mockResolvedValue([workspace]);
|
|
39
|
-
listAgentPresetsMock.mockResolvedValue({ presets: [], authorable: false, hasDocument: false });
|
|
40
|
-
createSessionMock.mockResolvedValue({ sessionId: 's-new' });
|
|
41
|
-
});
|
|
42
|
-
afterEach(() => {
|
|
43
|
-
cleanup();
|
|
44
|
-
vi.clearAllMocks();
|
|
45
|
-
});
|
|
46
|
-
describe('SessionListView roster', () => {
|
|
47
|
-
it('renders the sessions owned by the workspace', async () => {
|
|
48
|
-
listSessionsMock.mockResolvedValue({
|
|
49
|
-
items: [
|
|
50
|
-
summary('s-1', 1_700_000_000_000, { projections: { values: { title: '改造移动端' } } }),
|
|
51
|
-
summary('s-other', 1_600_000_000_000, { cwd: '/tmp/foreign' }),
|
|
52
|
-
],
|
|
53
|
-
hasMore: false,
|
|
54
|
-
});
|
|
55
|
-
renderList();
|
|
56
|
-
expect(await screen.findByText('改造移动端')).toBeTruthy();
|
|
57
|
-
// The foreign session (not in workspace.sessionIds) stays hidden.
|
|
58
|
-
expect(screen.queryByText(/foreign/)).toBeNull();
|
|
59
|
-
});
|
|
60
|
-
it('shows the empty state when the workspace owns no sessions', async () => {
|
|
61
|
-
renderList();
|
|
62
|
-
expect(await screen.findByText(/还没有会话/)).toBeTruthy();
|
|
63
|
-
});
|
|
64
|
-
it('refreshes the owned-id set from workspace.list on mount', async () => {
|
|
65
|
-
listSessionsMock.mockResolvedValue({
|
|
66
|
-
items: [summary('s-2', 1_700_000_000_000, { cwd: '/tmp/other' })],
|
|
67
|
-
hasMore: false,
|
|
68
|
-
});
|
|
69
|
-
listWorkspacesMock.mockResolvedValue([
|
|
70
|
-
{ ...workspace, sessionIds: ['s-2'] },
|
|
71
|
-
]);
|
|
72
|
-
renderList();
|
|
73
|
-
// s-2 is absent from the stale row but present in the fresh roster.
|
|
74
|
-
expect(await screen.findByText(/other/)).toBeTruthy();
|
|
75
|
-
});
|
|
76
|
-
});
|
|
77
|
-
describe('SessionListView creation', () => {
|
|
78
|
-
it('creates a blank session in the workspace and opens it immediately', async () => {
|
|
79
|
-
renderList();
|
|
80
|
-
await screen.findByText(/还没有会话/);
|
|
81
|
-
fireEvent.click(screen.getByRole('button', { name: '+ 新建会话' }));
|
|
82
|
-
await waitFor(() => {
|
|
83
|
-
expect(createSessionMock).toHaveBeenCalledWith({ workspaceId: 'w-1' });
|
|
84
|
-
});
|
|
85
|
-
await waitFor(() => {
|
|
86
|
-
expect(picked).toBeDefined();
|
|
87
|
-
});
|
|
88
|
-
expect(picked?.sessionId).toBe('s-new');
|
|
89
|
-
expect(picked?.blank).toBe(true);
|
|
90
|
-
// The fresh row is prepended without waiting for a refetch.
|
|
91
|
-
expect(await screen.findByText('新会话')).toBeTruthy();
|
|
92
|
-
});
|
|
93
|
-
it('creates the session with the selected agent preset', async () => {
|
|
94
|
-
listAgentPresetsMock.mockResolvedValue({
|
|
95
|
-
presets: [
|
|
96
|
-
{ id: 'router-standard', name: 'Router Standard', trust: 'system', isDefault: true },
|
|
97
|
-
{ id: 'router-spec', name: 'Router Spec', description: '规格驱动模式', trust: 'system', isDefault: false },
|
|
98
|
-
],
|
|
99
|
-
authorable: false,
|
|
100
|
-
hasDocument: false,
|
|
101
|
-
});
|
|
102
|
-
renderList();
|
|
103
|
-
const picker = await screen.findByRole('combobox', { name: 'Agent 模式' });
|
|
104
|
-
expect(picker.value).toBe('router-standard');
|
|
105
|
-
fireEvent.change(picker, { target: { value: 'router-spec' } });
|
|
106
|
-
fireEvent.click(screen.getByRole('button', { name: '+ 新建会话' }));
|
|
107
|
-
await waitFor(() => {
|
|
108
|
-
expect(createSessionMock).toHaveBeenCalledWith({ workspaceId: 'w-1', agentPreset: 'router-spec' });
|
|
109
|
-
});
|
|
110
|
-
expect(await screen.findByText('规格驱动模式')).toBeTruthy();
|
|
111
|
-
});
|
|
112
|
-
it('skips a broken default preset and disables it in the picker', async () => {
|
|
113
|
-
listAgentPresetsMock.mockResolvedValue({
|
|
114
|
-
presets: [
|
|
115
|
-
{ id: 'broken-default', trust: 'system', isDefault: true, broken: 'missing plugin' },
|
|
116
|
-
{ id: 'local-router', name: 'Local Router', trust: 'user', isDefault: false },
|
|
117
|
-
],
|
|
118
|
-
authorable: false,
|
|
119
|
-
hasDocument: false,
|
|
120
|
-
});
|
|
121
|
-
renderList();
|
|
122
|
-
const picker = await screen.findByRole('combobox', { name: 'Agent 模式' });
|
|
123
|
-
expect(picker.value).toBe('local-router');
|
|
124
|
-
expect(screen.getByRole('option', { name: 'broken-default(默认)(不可用)' }).hasAttribute('disabled')).toBe(true);
|
|
125
|
-
expect(screen.getByRole('option', { name: 'Local Router(本地)' })).toBeTruthy();
|
|
126
|
-
});
|
|
127
|
-
it('keeps default session creation available when the preset roster fails', async () => {
|
|
128
|
-
listAgentPresetsMock.mockRejectedValue(new Error('preset roster unavailable'));
|
|
129
|
-
renderList();
|
|
130
|
-
await screen.findByText(/还没有会话/);
|
|
131
|
-
const button = await screen.findByRole('button', { name: '+ 新建会话' });
|
|
132
|
-
await waitFor(() => { expect(button.hasAttribute('disabled')).toBe(false); });
|
|
133
|
-
fireEvent.click(button);
|
|
134
|
-
await waitFor(() => {
|
|
135
|
-
expect(createSessionMock).toHaveBeenCalledWith({ workspaceId: 'w-1' });
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
it('waits for the preset roster before enabling session creation', async () => {
|
|
139
|
-
let resolvePresets = () => { };
|
|
140
|
-
listAgentPresetsMock.mockReturnValue(new Promise(resolve => { resolvePresets = resolve; }));
|
|
141
|
-
renderList();
|
|
142
|
-
const button = screen.getByRole('button', { name: '+ 新建会话' });
|
|
143
|
-
expect(button.hasAttribute('disabled')).toBe(true);
|
|
144
|
-
resolvePresets({ presets: [], authorable: false, hasDocument: false });
|
|
145
|
-
await waitFor(() => { expect(button.hasAttribute('disabled')).toBe(false); });
|
|
146
|
-
});
|
|
147
|
-
it('keeps the button disabled while a creation is in flight', async () => {
|
|
148
|
-
let resolveCreate = () => { };
|
|
149
|
-
createSessionMock.mockReturnValue(new Promise(resolve => { resolveCreate = resolve; }));
|
|
150
|
-
renderList();
|
|
151
|
-
await screen.findByText(/还没有会话/);
|
|
152
|
-
fireEvent.click(screen.getByRole('button', { name: '+ 新建会话' }));
|
|
153
|
-
const button = await screen.findByRole('button', { name: '创建中…' });
|
|
154
|
-
expect(button.hasAttribute('disabled')).toBe(true);
|
|
155
|
-
resolveCreate({ sessionId: 's-new' });
|
|
156
|
-
await waitFor(() => { expect(picked).toBeDefined(); });
|
|
157
|
-
});
|
|
158
|
-
it('shows the stale-host hint when creation is refused with HTTP 403', async () => {
|
|
159
|
-
createSessionMock.mockRejectedValue(new Error('HTTP 403'));
|
|
160
|
-
renderList();
|
|
161
|
-
await screen.findByText(/还没有会话/);
|
|
162
|
-
fireEvent.click(screen.getByRole('button', { name: '+ 新建会话' }));
|
|
163
|
-
expect(await screen.findByText(/HTTP 403/)).toBeTruthy();
|
|
164
|
-
expect(await screen.findByText(/重启 dsh web/)).toBeTruthy();
|
|
165
|
-
expect(picked).toBeUndefined();
|
|
166
|
-
});
|
|
167
|
-
});
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Landing level: the workspace roster. The mobile surface opens straight
|
|
3
|
-
* here — no new-session homepage — and every workspace row is a thin
|
|
4
|
-
* fetch from workspace.list (the roster is small; sessions are not loaded
|
|
5
|
-
* until a workspace is opened).
|
|
6
|
-
*/
|
|
7
|
-
import type { WorkspaceView as WorkspaceRow } from '@deepseek-ai/dsh-host-apiproxy/api/workspace';
|
|
8
|
-
/** Props for the workspace roster. */
|
|
9
|
-
export interface WorkspaceViewProps {
|
|
10
|
-
/** Workspace carried by the pairing link; opened after the roster loads. */
|
|
11
|
-
initialWorkspaceId?: string;
|
|
12
|
-
/** Open one workspace's session list. */
|
|
13
|
-
onPick(workspace: WorkspaceRow): void;
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Render the workspace roster.
|
|
17
|
-
* @param props - the pick action.
|
|
18
|
-
* @returns the roster.
|
|
19
|
-
*/
|
|
20
|
-
export declare function WorkspaceView({ initialWorkspaceId, onPick }: WorkspaceViewProps): import("react").JSX.Element;
|
|
21
|
-
//# sourceMappingURL=WorkspaceView.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"WorkspaceView.d.ts","sourceRoot":"","sources":["../../../../src/mobile/views/WorkspaceView.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,aAAa,IAAI,YAAY,EAAE,MAAM,8CAA8C,CAAA;AAwHjG,sCAAsC;AACtC,MAAM,WAAW,kBAAkB;IACjC,4EAA4E;IAC5E,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,yCAAyC;IACzC,MAAM,CAAC,SAAS,EAAE,YAAY,GAAG,IAAI,CAAA;CACtC;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,EAAE,kBAAkB,EAAE,MAAM,EAAE,EAAE,kBAAkB,+BAuG/E"}
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
/**
|
|
3
|
-
* Landing level: the workspace roster. The mobile surface opens straight
|
|
4
|
-
* here — no new-session homepage — and every workspace row is a thin
|
|
5
|
-
* fetch from workspace.list (the roster is small; sessions are not loaded
|
|
6
|
-
* until a workspace is opened).
|
|
7
|
-
*/
|
|
8
|
-
import { useEffect, useState } from 'react';
|
|
9
|
-
import { listWorkspaces, listDirectory, createWorkspace } from "../api.js";
|
|
10
|
-
import { errorText } from "./App.js";
|
|
11
|
-
import { ThemeToggle } from "../theme-toggle.js";
|
|
12
|
-
/**
|
|
13
|
-
* Render the directory browser for creating a new workspace.
|
|
14
|
-
*/
|
|
15
|
-
function DirectoryBrowser({ onCancel, onPick }) {
|
|
16
|
-
const [listing, setListing] = useState(undefined);
|
|
17
|
-
const [error, setError] = useState(undefined);
|
|
18
|
-
const [creating, setCreating] = useState(false);
|
|
19
|
-
const [path, setPath] = useState(undefined);
|
|
20
|
-
const [reload, setReload] = useState(0);
|
|
21
|
-
useEffect(() => {
|
|
22
|
-
let cancelled = false;
|
|
23
|
-
setError(undefined);
|
|
24
|
-
setListing(undefined);
|
|
25
|
-
void listDirectory(path).then((res) => {
|
|
26
|
-
if (cancelled)
|
|
27
|
-
return;
|
|
28
|
-
setListing(res);
|
|
29
|
-
}, (reason) => {
|
|
30
|
-
if (cancelled)
|
|
31
|
-
return;
|
|
32
|
-
setError(errorText(reason));
|
|
33
|
-
});
|
|
34
|
-
return () => { cancelled = true; };
|
|
35
|
-
}, [path, reload]);
|
|
36
|
-
const handleCreate = async () => {
|
|
37
|
-
if (listing === undefined || creating)
|
|
38
|
-
return;
|
|
39
|
-
setCreating(true);
|
|
40
|
-
setError(undefined);
|
|
41
|
-
try {
|
|
42
|
-
const result = await createWorkspace(listing.path);
|
|
43
|
-
onPick(result.workspace);
|
|
44
|
-
}
|
|
45
|
-
catch (reason) {
|
|
46
|
-
setCreating(false);
|
|
47
|
-
setError(errorText(reason));
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
return (_jsxs("div", { className: "mobile dir-browser", children: [_jsxs("header", { className: "mobile-header", children: [_jsx("button", { type: "button", className: "mobile-headerAction", onClick: onCancel, children: "\u8FD4\u56DE" }), _jsx("h1", { className: "mobile-title", children: "\u9009\u62E9\u76EE\u5F55" })] }), listing !== undefined && (_jsx("div", { className: "dir-crumbs", children: listing.crumbs.map((crumb, idx) => (_jsxs("span", { children: [_jsx("button", { type: "button", className: "dir-crumb", onClick: () => setPath(crumb.path), children: crumb.name || '/' }), idx < listing.crumbs.length - 1 && _jsx("span", { className: "dir-crumb-separator", children: "/" })] }, crumb.path))) })), error !== undefined ? (_jsxs("div", { className: "mobile-empty", children: [_jsx("p", { className: "mobile-error", children: error }), _jsx("button", { type: "button", className: "mobile-button", onClick: () => setReload(n => n + 1), children: "\u91CD\u8BD5" })] })) : listing === undefined ? (_jsx("div", { className: "mobile-empty", children: _jsx("p", { className: "mobile-muted", children: "\u52A0\u8F7D\u4E2D\u2026" }) })) : (_jsx("ul", { className: "mobile-list", children: listing.entries.length === 0 ? (_jsx("div", { className: "mobile-empty dir-empty", children: _jsx("p", { className: "mobile-muted", children: "\u7A7A\u76EE\u5F55" }) })) : (listing.entries.map((entry) => (_jsx("li", { children: _jsx("button", { type: "button", className: `mobile-row dir-entry ${entry.hidden ? 'dir-entry-hidden' : ''}`, onClick: () => setPath(entry.path), children: _jsx("span", { className: "mobile-rowTitle", children: entry.name }) }) }, entry.path)))) })), _jsx("div", { className: "dir-select", children: _jsx("button", { type: "button", className: "mobile-button", disabled: listing === undefined || creating, onClick: () => void handleCreate(), children: creating ? '创建中…' : '选择此目录' }) })] }));
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Render the workspace roster.
|
|
54
|
-
* @param props - the pick action.
|
|
55
|
-
* @returns the roster.
|
|
56
|
-
*/
|
|
57
|
-
export function WorkspaceView({ initialWorkspaceId, onPick }) {
|
|
58
|
-
const [items, setItems] = useState(undefined);
|
|
59
|
-
const [error, setError] = useState(undefined);
|
|
60
|
-
// Bumped by the retry button to re-run the roster fetch effect.
|
|
61
|
-
const [reload, setReload] = useState(0);
|
|
62
|
-
const [isCreating, setIsCreating] = useState(false);
|
|
63
|
-
useEffect(() => {
|
|
64
|
-
let cancelled = false;
|
|
65
|
-
void listWorkspaces().then((rows) => {
|
|
66
|
-
if (cancelled)
|
|
67
|
-
return;
|
|
68
|
-
const target = initialWorkspaceId === undefined
|
|
69
|
-
? undefined
|
|
70
|
-
: rows.find(workspace => workspace.workspaceId === initialWorkspaceId);
|
|
71
|
-
if (target !== undefined) {
|
|
72
|
-
onPick(target);
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
setItems(rows);
|
|
76
|
-
}, (reason) => {
|
|
77
|
-
if (cancelled)
|
|
78
|
-
return;
|
|
79
|
-
setError(errorText(reason));
|
|
80
|
-
});
|
|
81
|
-
return () => { cancelled = true; };
|
|
82
|
-
}, [initialWorkspaceId, onPick, reload]);
|
|
83
|
-
if (isCreating) {
|
|
84
|
-
return (_jsx(DirectoryBrowser, { onCancel: () => setIsCreating(false), onPick: onPick }));
|
|
85
|
-
}
|
|
86
|
-
if (error !== undefined) {
|
|
87
|
-
return (_jsxs("div", { className: "mobile", children: [_jsxs("header", { className: "mobile-header", children: [_jsx("h1", { className: "mobile-title", children: "\u5DE5\u4F5C\u533A" }), _jsx(ThemeToggle, {})] }), _jsxs("div", { className: "mobile-empty", children: [_jsxs("p", { className: "mobile-error", children: ["\u52A0\u8F7D\u5931\u8D25\uFF1A", error] }), _jsx("button", { type: "button", className: "mobile-button", onClick: () => { setError(undefined); setItems(undefined); setReload(n => n + 1); }, children: "\u91CD\u8BD5" })] })] }));
|
|
88
|
-
}
|
|
89
|
-
if (items === undefined) {
|
|
90
|
-
return (_jsxs("div", { className: "mobile", children: [_jsxs("header", { className: "mobile-header", children: [_jsx("h1", { className: "mobile-title", children: "\u5DE5\u4F5C\u533A" }), _jsx(ThemeToggle, {})] }), _jsx("div", { className: "mobile-empty", children: _jsx("p", { className: "mobile-muted", children: "\u52A0\u8F7D\u4E2D\u2026" }) })] }));
|
|
91
|
-
}
|
|
92
|
-
return (_jsxs("div", { className: "mobile", children: [_jsxs("header", { className: "mobile-header", children: [_jsx("h1", { className: "mobile-title", children: "\u5DE5\u4F5C\u533A" }), _jsx(ThemeToggle, {})] }), items.length === 0 ? (_jsx("div", { className: "mobile-empty", children: _jsx("p", { className: "mobile-muted", children: "\u6682\u65E0\u5DE5\u4F5C\u533A" }) })) : (_jsx("ul", { className: "mobile-list", children: items.map(workspace => (_jsx("li", { children: _jsxs("button", { type: "button", className: "mobile-row", onClick: () => { onPick(workspace); }, children: [_jsx("span", { className: "mobile-rowTitle", children: workspace.title }), _jsx("span", { className: "mobile-rowMeta", children: workspace.path }), _jsx("span", { className: "mobile-chevron", children: "\u203A" })] }) }, workspace.workspaceId))) })), _jsx("div", { style: { padding: '16px' }, children: _jsx("button", { type: "button", className: "mobile-button", onClick: () => setIsCreating(true), children: "+ \u65B0\u5EFA\u5DE5\u4F5C\u533A" }) })] }));
|
|
93
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"WorkspaceView.test.d.ts","sourceRoot":"","sources":["../../../../src/mobile/views/WorkspaceView.test.tsx"],"names":[],"mappings":""}
|