Skip to content

Commit 0764ce3

Browse files
committed
feat: implement step-by-step user onboarding
1 parent de0f26c commit 0764ce3

File tree

10 files changed

+302
-13
lines changed

10 files changed

+302
-13
lines changed

xinference/web/ui/package-lock.json

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

xinference/web/ui/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"react-cookie": "^6.1.1",
3333
"react-dom": "^18.2.0",
3434
"react-i18next": "^15.4.0",
35+
"react-joyride": "^2.9.3",
3536
"react-pro-sidebar": "^1.1.0-alpha.1",
3637
"react-router-dom": "^6.14.1",
3738
"react-scripts": "5.0.1",

xinference/web/ui/src/components/MenuSide.js

Lines changed: 109 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
} from '@mui/material'
2222
import { useEffect, useState } from 'react'
2323
import { useTranslation } from 'react-i18next'
24+
import Joyride, { STATUS } from 'react-joyride'
2425
import { useLocation, useNavigate } from 'react-router-dom'
2526

2627
import icon from '../media/icon.webp'
@@ -36,6 +37,10 @@ const MenuSide = () => {
3637
const [drawerWidth, setDrawerWidth] = useState(
3738
`${Math.min(Math.max(window.innerWidth * 0.2, 287), 320)}px`
3839
)
40+
const [run, setRun] = useState(false)
41+
const [stepIndex, setStepIndex] = useState(0)
42+
const [spotlightClicks, setSpotlightClicks] = useState(false)
43+
3944
const { i18n, t } = useTranslation()
4045

4146
const navItems = [
@@ -71,11 +76,51 @@ const MenuSide = () => {
7176
},
7277
]
7378

79+
const clickableSteps = [4, 5]
80+
81+
const steps = [
82+
{
83+
target: '.step-0',
84+
content: t('components.step-0'),
85+
disableBeacon: true,
86+
},
87+
{
88+
target: '.step-1',
89+
content: t('components.step-1'),
90+
disableBeacon: true,
91+
},
92+
{
93+
target: '.step-2',
94+
content: t('components.step-2'),
95+
disableBeacon: true,
96+
},
97+
{
98+
target: '.step-3',
99+
content: t('components.step-3'),
100+
disableBeacon: true,
101+
},
102+
{
103+
target: '.step-4',
104+
content: t('components.step-4'),
105+
disableBeacon: true,
106+
},
107+
{
108+
target: '.step-5',
109+
content: t('components.step-5'),
110+
disableBeacon: true,
111+
},
112+
]
113+
74114
useEffect(() => {
75115
setActive(pathname.substring(1))
76116
}, [pathname])
77117

78118
useEffect(() => {
119+
if (!localStorage.getItem('hasSeenGuide')) {
120+
setRun(true)
121+
localStorage.setItem('hasSeenGuide', 'true')
122+
}
123+
79124
const screenWidth = window.innerWidth
80125
const maxDrawerWidth = Math.min(Math.max(screenWidth * 0.2, 287), 320)
81126
setDrawerWidth(`${maxDrawerWidth}px`)
@@ -96,6 +141,40 @@ const MenuSide = () => {
96141
}
97142
}, [])
98143

144+
const handleJoyrideCallback = (data) => {
145+
const { index, type, status, action } = data
146+
147+
if (type === 'step:before') {
148+
setStepIndex(index)
149+
setSpotlightClicks(clickableSteps.includes(index))
150+
}
151+
152+
if (status === STATUS.FINISHED || status === STATUS.SKIPPED) {
153+
setRun(false)
154+
setStepIndex(0)
155+
}
156+
157+
if (type === 'step:after') {
158+
if (action !== 'prev') {
159+
setStepIndex(index + 1)
160+
if (index === 3) {
161+
const target = document.querySelector('.step-3')
162+
if (target) {
163+
target.click()
164+
}
165+
}
166+
} else {
167+
setStepIndex(index - 1)
168+
if (index === 4) {
169+
const target = document.querySelector('.step-goBack')
170+
if (target) {
171+
target.click()
172+
}
173+
}
174+
}
175+
}
176+
}
177+
99178
return (
100179
<Drawer
101180
variant="permanent"
@@ -110,6 +189,35 @@ const MenuSide = () => {
110189
}}
111190
style={{ zIndex: 1 }}
112191
>
192+
<Joyride
193+
steps={steps}
194+
run={run}
195+
stepIndex={stepIndex}
196+
spotlightClicks={spotlightClicks}
197+
callback={handleJoyrideCallback}
198+
continuous
199+
showStepsProgress
200+
showSkipButton
201+
showProgress
202+
scrollToFirstStep
203+
disableScrolling
204+
hideCloseButton
205+
disableScrollParentFix
206+
locale={{
207+
back: t('components.back'),
208+
close: t('components.close'),
209+
next: t('components.next'),
210+
last: t('components.last'),
211+
skip: t('components.skip'),
212+
nextLabelWithProgress: t('components.next'),
213+
}}
214+
styles={{
215+
options: {
216+
zIndex: 1300,
217+
primaryColor: '#1976D2',
218+
},
219+
}}
220+
/>
113221
{/* Title */}
114222
<Box
115223
display="flex"
@@ -145,7 +253,7 @@ const MenuSide = () => {
145253
<Box sx={{ flexGrow: 1 }}>
146254
<Box width="100%">
147255
<Box m="1.5rem 2rem 2rem 3rem"></Box>
148-
<List>
256+
<List className="step-0">
149257
{navItems.map(({ text, label, icon }) => {
150258
if (!icon) {
151259
return (

xinference/web/ui/src/locales/en.json

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,17 @@
208208

209209
"components": {
210210
"copySuccess": "Copied to clipboard!",
211-
"suggestsCommonParameters": "Suggests common parameters; others allowed."
211+
"suggestsCommonParameters": "Suggests common parameters; others allowed.",
212+
"back": "Back",
213+
"close": "Close",
214+
"next": "Next",
215+
"last": "Finish",
216+
"skip": "Skip Tour",
217+
"step-0": "Menu bar, containing all main function entries.",
218+
"step-1": "Classify models by category for quick access.",
219+
"step-2": "Use the filter function to quickly locate the desired model.",
220+
"step-3": "Click to open the model parameter configuration window.",
221+
"step-4": "Configure relevant model parameters in this window.",
222+
"step-5": "After filling in the required parameters, click the button to start the model."
212223
}
213224
}

0 commit comments

Comments
 (0)