Skip to content

Commit 4bbe3a1

Browse files
Merge pull request #6 from esto-openscan/enhance-setup-wizard
Enhance setup wizard
2 parents 8d1b884 + f47b7b4 commit 4bbe3a1

3 files changed

Lines changed: 209 additions & 26 deletions

File tree

app/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "openscan3-client",
3-
"version": "0.1.2",
3+
"version": "0.1.3",
44
"apiVersion": "0.6.0",
55
"description": "Frontend client for OpenScan3 firmware devices",
66
"productName": "OpenScan3",

app/src/components/base/BaseWizard.vue

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ function isDone(stepId: string) {
115115
:caption="step.caption"
116116
:done="isDone(step.id)"
117117
>
118-
<div class="q-mt-md">
118+
<div class="q-mt-sm">
119119
<slot
120120
:step="step"
121121
:active-id="activeId"
@@ -132,7 +132,7 @@ function isDone(stepId: string) {
132132
:go-next="handleNext"
133133
:go-back="handleBack"
134134
>
135-
<div class="row items-center justify-between q-mt-md">
135+
<div class="row items-center justify-between q-mt-sm">
136136
<BaseButtonSecondary
137137
v-if="showBackButton && !isFirstStep"
138138
:label="backLabel"
@@ -151,6 +151,37 @@ function isDone(stepId: string) {
151151

152152
<style scoped>
153153
.wizard-stepper :deep(.q-stepper__tab) {
154-
min-width: 100px;
154+
min-width: 90px;
155+
}
156+
157+
.wizard-stepper :deep(.q-stepper__header) {
158+
flex-wrap: nowrap;
159+
overflow-x: auto;
160+
scrollbar-width: thin;
161+
}
162+
163+
.wizard-stepper :deep(.q-stepper__header) {
164+
padding-bottom: 0;
165+
}
166+
167+
.wizard-stepper :deep(.q-stepper__tab) {
168+
padding: 6px 10px;
169+
}
170+
171+
.wizard-stepper :deep(.q-stepper__tab--active) {
172+
background: rgba(0, 0, 0, 0.04);
173+
border-radius: 8px;
174+
}
175+
176+
.wizard-stepper :deep(.q-stepper__step-content) {
177+
padding-top: 8px;
178+
}
179+
180+
.wizard-stepper :deep(.q-stepper__header) {
181+
border-bottom: 0;
182+
}
183+
184+
.wizard-stepper :deep(.q-stepper__content) {
185+
border-top: 0;
155186
}
156187
</style>

app/src/pages/SetupPage.vue

Lines changed: 174 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
<template>
2-
<BasePage>
3-
<BaseSection
4-
title="Device setup"
5-
subtitle="Initial configuration of your OpenScan device."
2+
<BasePage content-class="col-12 col-md-10 col-lg-8">
3+
<div class="q-mb-lg">
4+
<div class="text-h5">Device setup</div>
5+
<div class="text-body2 text-grey-7">
6+
Initial configuration of your OpenScan device.
7+
</div>
8+
</div>
9+
<BaseWizard
10+
v-model="activeStepId"
11+
:steps="steps"
12+
finish-label="Finish setup"
13+
next-label="Next"
14+
back-label="Back"
15+
@finish="handleFinishSetup"
616
>
7-
<BaseWizard
8-
v-model="activeStepId"
9-
:steps="steps"
10-
finish-label="Finish setup"
11-
next-label="Next"
12-
back-label="Back"
13-
@finish="handleFinishSetup"
14-
>
1517
<template #default="{ step }">
1618
<div v-if="step.id === 'connection'">
1719
<div class="text-subtitle1 q-mb-sm">Device configuration</div>
@@ -43,6 +45,44 @@
4345
</BaseList>
4446
</div>
4547
</div>
48+
<div v-else-if="step.id === 'rotor-direction'">
49+
<div class="text-subtitle1 q-mb-sm">Rotor direction</div>
50+
<p class="q-mb-md">
51+
Use the buttons below to move the rotor slightly up or down and confirm the movement matches the labels.
52+
</p>
53+
<div class="rotor-controls q-mb-md">
54+
<BaseButtonSecondary
55+
icon="keyboard_arrow_up"
56+
label="Move up"
57+
:loading="rotorMoveAction === 'up'"
58+
:disable="isRotorControlDisabled"
59+
@click="handleRotorMove('up')"
60+
/>
61+
<BaseButtonSecondary
62+
icon="keyboard_arrow_down"
63+
label="Move down"
64+
:loading="rotorMoveAction === 'down'"
65+
:disable="isRotorControlDisabled"
66+
@click="handleRotorMove('down')"
67+
/>
68+
</div>
69+
<div class="text-body2 q-mb-sm">
70+
Does the rotor move in the direction shown on the buttons? If yes, click "Next". If not, click "Reverse direction".
71+
</div>
72+
<div class="rotor-reverse">
73+
<BaseButtonPrimary
74+
outline
75+
icon="swap_vert"
76+
label="Reverse direction"
77+
:loading="isReversingRotorDirection"
78+
:disable="isReverseDisabled"
79+
@click="handleReverseRotorDirection"
80+
/>
81+
</div>
82+
<div class="text-body2 text-grey-7 q-mt-sm text-center">
83+
Current direction: {{ rotorDirectionLabel }}
84+
</div>
85+
</div>
4686
<div v-else-if="step.id === 'hardware'">
4787
<div class="text-subtitle1 q-mb-sm">Rotor position</div>
4888
<p>Verify that the initial rotor position is correct as in the picture.</p>
@@ -74,7 +114,7 @@
74114
/>
75115
<BaseButtonSecondary
76116
icon="flip"
77-
label="Mirror"
117+
label="Mirror vertically"
78118
:loading="orientationAction === 'mirror'"
79119
:disable="isOrientationUpdating"
80120
size="md"
@@ -136,8 +176,7 @@
136176
</div>
137177
</div>
138178
</template>
139-
</BaseWizard>
140-
</BaseSection>
179+
</BaseWizard>
141180
</BasePage>
142181
</template>
143182

@@ -146,7 +185,6 @@ import { computed, onMounted, ref } from 'vue'
146185
import { useQuasar } from 'quasar'
147186
import { useRouter } from 'vue-router'
148187
import BasePage from 'components/base/BasePage.vue'
149-
import BaseSection from 'components/base/BaseSection.vue'
150188
import BaseWizard from 'components/base/BaseWizard.vue'
151189
import BaseList from 'components/base/BaseList.vue'
152190
import BaseListItem from 'components/base/BaseListItem.vue'
@@ -157,11 +195,19 @@ import motorPositionImage from 'src/assets/openscan_motor_position.jpg'
157195
import { useDeviceStore } from 'src/stores/device'
158196
import CameraFastPreview from 'components/camera/CameraFastPreview.vue'
159197
import { useCameraStore } from 'src/stores/camera'
160-
import { listConfigFiles, setConfigFile, updateCameraNameSettings, type DeviceConfigRequest } from 'src/generated/api'
198+
import {
199+
listConfigFiles,
200+
moveMotorByDegree,
201+
setConfigFile,
202+
updateCameraNameSettings,
203+
updateMotorNameSettings,
204+
type DeviceConfigRequest
205+
} from 'src/generated/api'
161206
import { apiClient } from 'src/services/apiClient'
162207
163208
const steps = [
164209
{ id: 'connection', label: 'Model', caption: 'Select the device model.' },
210+
{ id: 'rotor-direction', label: 'Rotor Direction', caption: 'Verify motor direction.' },
165211
{ id: 'hardware', label: 'Rotor Position', caption: 'Verify initial motor position.' },
166212
{ id: 'orientation', label: 'Camera Orientation', caption: 'Adjust camera orientation.' },
167213
{ id: 'test-scan', label: 'Finish', caption: '' }
@@ -194,6 +240,34 @@ const isNextDisabled = computed(
194240
isApplyingConfig.value
195241
)
196242
243+
const ROTOR_MOTOR_NAME = 'rotor'
244+
const rotorMoveAction = ref<'up' | 'down' | null>(null)
245+
const isReversingRotorDirection = ref(false)
246+
247+
const rotorMotor = computed(() => deviceStore.device?.motors?.[ROTOR_MOTOR_NAME] ?? null)
248+
const rotorDirection = computed<1 | -1 | null>(() => {
249+
const value = rotorMotor.value?.settings?.direction
250+
return value === 1 || value === -1 ? value : null
251+
})
252+
const rotorDirectionLabel = computed(() => {
253+
if (rotorDirection.value === 1) return 'Forward (1)'
254+
if (rotorDirection.value === -1) return 'Reverse (-1)'
255+
return 'Unknown'
256+
})
257+
258+
const isRotorControlDisabled = computed(
259+
() =>
260+
rotorMoveAction.value !== null ||
261+
isReversingRotorDirection.value ||
262+
deviceStore.status !== 'open'
263+
)
264+
const isReverseDisabled = computed(
265+
() =>
266+
isReversingRotorDirection.value ||
267+
deviceStore.status !== 'open' ||
268+
rotorDirection.value === null
269+
)
270+
197271
const orientationCamera = computed(() => {
198272
const options = cameraStore.cameraOptions
199273
if (!options.length) return null
@@ -218,7 +292,25 @@ async function loadConfigs() {
218292
try {
219293
const data = await listConfigFiles<true>({ client: apiClient, throwOnError: true })
220294
const configs = (data as any)?.configs ?? []
221-
configOptions.value = configs as DeviceConfigFile[]
295+
const filtered = (configs as DeviceConfigFile[]).filter(
296+
(config) => config.filename !== 'device_config.json'
297+
)
298+
299+
filtered.sort((a, b) => {
300+
const shieldA = (a.shield ?? '').toLowerCase()
301+
const shieldB = (b.shield ?? '').toLowerCase()
302+
if (shieldA !== shieldB) return shieldB.localeCompare(shieldA)
303+
304+
const nameA = (a.name ?? a.filename).toLowerCase()
305+
const nameB = (b.name ?? b.filename).toLowerCase()
306+
return nameA.localeCompare(nameB)
307+
})
308+
309+
configOptions.value = filtered
310+
311+
if (selectedConfigPath.value && !filtered.some((c) => c.path === selectedConfigPath.value)) {
312+
selectedConfigPath.value = null
313+
}
222314
} catch (error) {
223315
console.error('Failed to load device configurations', error)
224316
$q.notify({ type: 'negative', message: 'Failed to load device configurations' })
@@ -255,6 +347,55 @@ async function handleNext(goNext: () => void) {
255347
goNext()
256348
}
257349
350+
async function handleRotorMove(direction: 'up' | 'down') {
351+
if (rotorMoveAction.value !== null) {
352+
return
353+
}
354+
355+
rotorMoveAction.value = direction
356+
const degrees = direction === 'up' ? -10 : 10
357+
358+
try {
359+
await deviceStore.ensureConnected()
360+
await moveMotorByDegree<true>({
361+
client: apiClient,
362+
throwOnError: true,
363+
path: { motor_name: ROTOR_MOTOR_NAME },
364+
body: { degrees }
365+
})
366+
} catch (error) {
367+
console.error('Failed to move rotor motor', error)
368+
$q.notify({ type: 'negative', message: 'Failed to move rotor motor' })
369+
} finally {
370+
rotorMoveAction.value = null
371+
}
372+
}
373+
374+
async function handleReverseRotorDirection() {
375+
if (isReversingRotorDirection.value || rotorDirection.value === null) {
376+
return
377+
}
378+
379+
const nextDirection = rotorDirection.value === 1 ? -1 : 1
380+
isReversingRotorDirection.value = true
381+
382+
try {
383+
await deviceStore.ensureConnected()
384+
await updateMotorNameSettings<true>({
385+
client: apiClient,
386+
throwOnError: true,
387+
path: { name: ROTOR_MOTOR_NAME },
388+
body: { direction: nextDirection }
389+
})
390+
await deviceStore.refreshFromRest()
391+
} catch (error) {
392+
console.error('Failed to reverse rotor direction', error)
393+
$q.notify({ type: 'negative', message: 'Failed to reverse rotor direction' })
394+
} finally {
395+
isReversingRotorDirection.value = false
396+
}
397+
}
398+
258399
const ROTATE_RIGHT_MAP: Record<number, number> = {
259400
1: 6,
260401
6: 3,
@@ -271,10 +412,10 @@ const MIRROR_MAP: Record<number, number> = {
271412
2: 1,
272413
3: 4,
273414
4: 3,
274-
5: 8,
275-
8: 5,
276-
6: 7,
277-
7: 6
415+
5: 6,
416+
6: 5,
417+
7: 8,
418+
8: 7
278419
}
279420
280421
function getSafeOrientationFlag(): number | null {
@@ -347,6 +488,17 @@ async function handleFinishSetup() {
347488
margin: 0 auto;
348489
}
349490
491+
.rotor-controls {
492+
display: flex;
493+
justify-content: center;
494+
gap: 8px;
495+
}
496+
497+
.rotor-reverse {
498+
display: flex;
499+
justify-content: center;
500+
}
501+
350502
.rotor-image {
351503
display: block;
352504
max-width: 100%;

0 commit comments

Comments
 (0)