Skip to content

Commit fc006a5

Browse files
committed
perf: improve workspace onboarding guidance
1 parent 90c72ce commit fc006a5

15 files changed

Lines changed: 841 additions & 303 deletions

File tree

‎i18n/locales/en.json‎

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,13 +116,31 @@
116116
},
117117
"WorkspaceEmpty": {
118118
"LoginHint": "Sign in to view assets and connect",
119-
"ConnectAsset": "Right-click asset → Connect",
120-
"ConnectAssetHint": " to choose a connection method",
121-
"ExpandNode": "Right-click node → Expand all",
122-
"ExpandNodeHint": " to expand all assets under the node",
119+
"Shortcuts": "Session shortcuts",
123120
"SwitchSession": "Switch session",
124121
"Or": "or"
125122
},
123+
"WorkspaceTour": {
124+
"ConnectAssetTitle": "Right-click asset → Connect",
125+
"ConnectAssetDescription": "You can choose a connection method again",
126+
"ExpandNodeTitle": "Right-click node → Expand all",
127+
"ExpandNodeDescription": "Expand all assets under the node",
128+
"AddSessionTitle": "Click + in the header to create a session",
129+
"AddSessionDescription": "Search for an asset and open a new connection",
130+
"progress": "Step {current} of {total}",
131+
"next": "Next",
132+
"previous": "Previous",
133+
"done": "Done"
134+
},
135+
"AddSession": {
136+
"NewConnection": "New connection",
137+
"SearchPlaceholder": "Search by asset name or address…",
138+
"Local": "Local",
139+
"LocalShell": "Local Shell",
140+
"LocalShellHint": "Open a local terminal",
141+
"SearchResults": "Search results",
142+
"NoMatchingAssets": "No matching assets"
143+
},
126144
"Auth": {
127145
"SignInTitle": "Sign in from your browser to continue",
128146
"BrowserHintPrefix": "If your browser hasn't opened for you to sign in,",

‎i18n/locales/zh.json‎

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,13 +116,31 @@
116116
},
117117
"WorkspaceEmpty": {
118118
"LoginHint": "登录后查看资产并发起连接",
119-
"ConnectAsset": "右击资产 → 连接",
120-
"ConnectAssetHint": ",可重新选择连接方式",
121-
"ExpandNode": "右击节点 → 展开全部",
122-
"ExpandNodeHint": ",展开节点下的所有资产",
119+
"Shortcuts": "会话快捷键",
123120
"SwitchSession": "切换会话",
124121
"Or": "或"
125122
},
123+
"WorkspaceTour": {
124+
"ConnectAssetTitle": "右击资产 → 连接",
125+
"ConnectAssetDescription": "可重新选择连接方式",
126+
"ExpandNodeTitle": "右击节点 → 展开全部",
127+
"ExpandNodeDescription": "展开该节点下的所有资产",
128+
"AddSessionTitle": "点击顶栏 + 新建会话",
129+
"AddSessionDescription": "搜索资产并打开新的连接",
130+
"progress": "第 {current} / {total} 步",
131+
"next": "下一步",
132+
"previous": "上一步",
133+
"done": "完成"
134+
},
135+
"AddSession": {
136+
"NewConnection": "新建连接",
137+
"SearchPlaceholder": "搜索资产名称或地址…",
138+
"Local": "本地",
139+
"LocalShell": "Local Shell",
140+
"LocalShellHint": "打开本机终端",
141+
"SearchResults": "搜索结果",
142+
"NoMatchingAssets": "未找到匹配资产"
143+
},
126144
"Auth": {
127145
"SignInTitle": "请在浏览器中完成登录",
128146
"BrowserHintPrefix": "如果浏览器没有自动为你打开登录页面",

‎nuxt.config.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ export default defineNuxtConfig({
8282
mode: "out-in"
8383
}
8484
},
85-
css: ["@/assets/css/main.css"],
85+
css: ["@/assets/css/main.css", "@/assets/css/workspace-tour.css"],
8686
icon: {
8787
provider: "none",
8888
fallbackToApi: false,

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@
7777
"@jumpserver/online-player": "workspace:*",
7878
"@lezer/highlight": "^1.2.3",
7979
"@nuxt/icon": "2.2.2",
80-
"@nuxt/ui": "4.8.0",
80+
"@nuxt/ui": "4.11.0",
8181
"@nuxtjs/i18n": "10.4.0",
8282
"@pinia/nuxt": "^0.11.3",
8383
"@uiw/codemirror-theme-dracula": "^4.25.11",

‎pnpm-lock.yaml‎

Lines changed: 453 additions & 237 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎ui/assets/css/workspace-tour.css‎

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
.driver-popover.workspace-driver-popover {
2+
box-sizing: border-box;
3+
width: min(320px, calc(100vw - 24px));
4+
min-width: 260px;
5+
max-width: 320px;
6+
border: 1px solid var(--app-border-strong);
7+
border-radius: 8px;
8+
background: var(--app-surface-overlay);
9+
color: var(--app-fg);
10+
box-shadow: 0 14px 36px color-mix(in srgb, #000 34%, transparent);
11+
font-family: inherit;
12+
padding: 14px;
13+
}
14+
15+
.driver-popover.workspace-driver-popover .driver-popover-title {
16+
padding-right: 32px;
17+
font-size: 14px;
18+
font-weight: 600;
19+
line-height: 1.4;
20+
color: var(--app-fg);
21+
}
22+
23+
.driver-popover.workspace-driver-popover .driver-popover-description {
24+
margin-top: 6px;
25+
color: var(--app-muted);
26+
font-size: 12px;
27+
line-height: 1.65;
28+
}
29+
30+
.driver-popover.workspace-driver-popover .driver-popover-close-btn {
31+
all: unset;
32+
box-sizing: border-box;
33+
position: absolute;
34+
top: 6px;
35+
right: 6px;
36+
display: grid;
37+
place-items: center;
38+
width: 22px;
39+
height: 22px;
40+
cursor: pointer;
41+
color: var(--app-muted);
42+
font-size: 16px;
43+
line-height: 1;
44+
}
45+
46+
.driver-popover.workspace-driver-popover .driver-popover-close-btn:hover,
47+
.driver-popover.workspace-driver-popover .driver-popover-close-btn:focus {
48+
color: var(--app-fg);
49+
}
50+
51+
.driver-popover.workspace-driver-popover .driver-popover-footer {
52+
display: flex;
53+
flex-wrap: wrap;
54+
align-items: center;
55+
justify-content: space-between;
56+
gap: 8px;
57+
margin-top: 13px;
58+
}
59+
60+
.driver-popover.workspace-driver-popover .driver-popover-progress-text {
61+
color: var(--app-muted);
62+
font-size: 11px;
63+
}
64+
65+
.driver-popover.workspace-driver-popover .driver-popover-navigation-btns {
66+
display: flex;
67+
flex-wrap: wrap;
68+
justify-content: flex-end;
69+
flex-grow: 0;
70+
gap: 4px;
71+
}
72+
73+
.driver-popover.workspace-driver-popover .driver-popover-footer-btn {
74+
all: unset;
75+
appearance: none;
76+
box-sizing: border-box;
77+
display: inline-flex;
78+
align-items: center;
79+
justify-content: center;
80+
min-height: 27px;
81+
padding: 4px 9px;
82+
border: 1px solid var(--app-border-strong);
83+
border-radius: 5px;
84+
background: var(--app-panel-bg);
85+
color: var(--app-fg);
86+
font-size: 11.5px;
87+
line-height: 1.2;
88+
cursor: pointer;
89+
}
90+
91+
.driver-popover.workspace-driver-popover .driver-popover-footer-btn:hover,
92+
.driver-popover.workspace-driver-popover .driver-popover-footer-btn:focus {
93+
background: var(--app-hover-strong);
94+
}
95+
96+
.driver-popover.workspace-driver-popover .driver-popover-next-btn {
97+
border-color: var(--theme-accent);
98+
background: var(--theme-accent);
99+
color: var(--app-accent-foreground);
100+
}
101+
102+
.driver-popover.workspace-driver-popover .driver-popover-next-btn:hover,
103+
.driver-popover.workspace-driver-popover .driver-popover-next-btn:focus {
104+
background: color-mix(in srgb, var(--theme-accent) 86%, var(--app-fg));
105+
}
106+
107+
.driver-popover.workspace-driver-popover .driver-popover-arrow {
108+
border-color: var(--app-surface-overlay);
109+
}

‎ui/chen/components/WorkspaceModal.vue‎

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@ const props = defineProps<{
77
ui?: ModalProps["ui"];
88
}>();
99
10-
const workspaceUi = computed<ModalProps["ui"]>(() => ({
11-
...props.ui,
12-
overlay: ["!absolute z-40 bg-elevated/75 backdrop-blur-none", props.ui?.overlay],
13-
content: ["!absolute z-40 !w-[calc(100%-2rem)] !max-h-[calc(100%-2rem)]", props.ui?.content]
14-
}));
10+
const workspaceUi = computed(
11+
() =>
12+
({
13+
...props.ui,
14+
overlay: ["!absolute z-40 bg-elevated/75 backdrop-blur-none", props.ui?.overlay],
15+
content: ["!absolute z-40 !w-[calc(100%-2rem)] !max-h-[calc(100%-2rem)]", props.ui?.content]
16+
}) as ModalProps["ui"]
17+
);
1518
</script>
1619

1720
<template>

‎ui/components/SideBar/assetTree.vue‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,7 @@ defineExpose({ refresh, loading });
459459
:class="open === false ? 'h-8 shrink-0' : 'min-h-0 flex-1'"
460460
role="tree"
461461
:aria-label="t('Menu.Resource')"
462+
:data-workspace-tour="search.trim() ? undefined : 'assets'"
462463
>
463464
<div
464465
v-if="!loggedIn"

‎ui/components/SideBar/assetTreeNode.vue‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ const { t } = useI18n();
2323
const isParent = computed(() => Boolean(props.node.isParent || props.node.children?.length));
2424
const isOpen = computed(() => Boolean(props.node.open));
2525
const isChecked = computed(() => props.checkedAssetIds?.includes(props.node.id) || false);
26+
const workspaceTourTarget = computed(() => {
27+
if (props.searchMode || props.batchMode) return undefined;
28+
if (isParent.value && props.node.meta?.type !== "recent-connections") return "node";
29+
return undefined;
30+
});
2631
const iconCandidates = computed(() => {
2732
const iconSkin = (props.node.iconSkin || "").toLowerCase();
2833
const data = props.node.meta?.data || {};
@@ -127,6 +132,7 @@ const activate = () => {
127132
]"
128133
:style="{ paddingLeft: `${10 + (node.level || 0) * 14}px` }"
129134
:title="node.title || node.name"
135+
:data-workspace-tour="workspaceTourTarget"
130136
@click="activate"
131137
@contextmenu.prevent="emit('contextmenu', node, $event)"
132138
>

‎ui/components/Workspace/addSessionPopover.vue‎

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ const { fetchTree, treeNodeToAsset } = useAssetTree();
1010
const { recentConnections } = useRecentConnections();
1111
const userInfoStore = useUserInfoStore();
1212
const { openLocalShell } = useWorkspaceTabs();
13+
const { t } = useI18n();
1314
const localShellAvailable = computed(() => isDesktopRuntime());
15+
const listTitle = computed(() => (search.value.trim() ? t("AddSession.SearchResults") : t("Menu.RecentConnections")));
16+
const listEmpty = computed(() =>
17+
search.value.trim() ? t("AddSession.NoMatchingAssets") : t("RecentConnections.Empty")
18+
);
1419
1520
function flatten(nodes: AssetTreeNode[]): AssetItem[] {
1621
return nodes.flatMap((node) => (node.isParent ? flatten(node.children || []) : [treeNodeToAsset(node)]));
@@ -52,32 +57,35 @@ watch(open, (value) => {
5257

5358
<template>
5459
<UPopover v-model:open="open" :content="{ align: 'start', side: 'bottom', sideOffset: 8 }" :ui="{ content: 'p-0' }">
55-
<UTooltip text="新建连接" :delay-duration="150">
60+
<UTooltip :text="t('AddSession.NewConnection')" :delay-duration="150">
5661
<button
5762
type="button"
58-
class="grid size-6 shrink-0 place-items-center rounded-lg text-muted transition-colors hover:bg-[var(--app-hover-strong)] hover:text-highlighted"
59-
aria-label="新建连接"
63+
data-workspace-tour="add-session"
64+
class="grid size-6 shrink-0 place-items-center rounded-lg text-muted transition-colors hover:bg-(--app-hover-strong) hover:text-highlighted"
65+
:aria-label="t('AddSession.NewConnection')"
6066
>
6167
<UIcon name="i-lucide-plus" class="size-4" />
6268
</button>
6369
</UTooltip>
6470

6571
<template #content>
66-
<div class="w-[360px] overflow-hidden rounded-xl bg-default shadow-xl ring-1 ring-black/10 dark:ring-white/12">
72+
<div class="w-90 overflow-hidden rounded-xl bg-default shadow-xl ring-1 ring-black/10 dark:ring-white/12">
6773
<div class="border-b border-default p-2">
6874
<UInput
6975
v-model="search"
7076
autofocus
7177
icon="i-lucide-search"
7278
size="sm"
7379
variant="none"
74-
placeholder="搜索资产名称或地址…"
80+
:placeholder="t('AddSession.SearchPlaceholder')"
7581
:ui="{ base: 'h-8 rounded-lg bg-elevated/70 ring-1 ring-inset ring-default' }"
7682
/>
7783
</div>
7884
<div class="max-h-80 min-h-32 overflow-y-auto p-1.5">
7985
<template v-if="localShellAvailable && !search.trim()">
80-
<div class="px-2 py-1 text-[10px] font-semibold uppercase tracking-wider text-muted">本地</div>
86+
<div class="px-2 py-1 text-[10px] font-semibold uppercase tracking-wider text-muted">
87+
{{ t("AddSession.Local") }}
88+
</div>
8189
<button
8290
type="button"
8391
class="flex w-full items-center gap-3 rounded-lg px-2 py-2 text-left hover:bg-elevated"
@@ -87,14 +95,14 @@ watch(open, (value) => {
8795
<UIcon name="i-lucide-square-terminal" class="size-4 text-muted" />
8896
</span>
8997
<span class="min-w-0 flex-1">
90-
<span class="block truncate text-sm text-highlighted">Local Shell</span>
91-
<span class="block truncate text-[11px] text-muted">打开本机终端</span>
98+
<span class="block truncate text-sm text-highlighted">{{ t("AddSession.LocalShell") }}</span>
99+
<span class="block truncate text-[11px] text-muted">{{ t("AddSession.LocalShellHint") }}</span>
92100
</span>
93101
<UIcon name="i-lucide-chevron-right" class="size-3.5 shrink-0 text-dimmed" />
94102
</button>
95103
</template>
96104
<div class="px-2 py-1 text-[10px] font-semibold uppercase tracking-wider text-muted">
97-
{{ search.trim() ? "搜索结果" : "最近连接" }}
105+
{{ listTitle }}
98106
</div>
99107
<div v-if="loading" class="grid h-24 place-items-center">
100108
<UIcon name="i-lucide-loader-circle" class="size-4 animate-spin text-muted" />
@@ -122,7 +130,7 @@ watch(open, (value) => {
122130
v-if="!loading && (search.trim() ? results : recentConnections).length === 0"
123131
class="grid h-24 place-items-center text-xs text-muted"
124132
>
125-
{{ search.trim() ? "未找到匹配资产" : "暂无最近连接" }}
133+
{{ listEmpty }}
126134
</div>
127135
</div>
128136
</div>

0 commit comments

Comments
 (0)