Skip to content

Commit 1e67bb7

Browse files
Minor UI fixes (#51)
- Add hidden span with autofocus to avoid app bar icons retaining focus - Remove y padding for app bar menu items - Make autocomplete search bottom and list top flat to keep them aligned - Prevent widget form submit when only single input is present (dihedral plots) - Handle redirects for invalid uuid's for Widget and Notebook page - Add notes for Energy widgets
1 parent 6847ecd commit 1e67bb7

8 files changed

Lines changed: 59 additions & 15 deletions

File tree

mdadash/backend/analyses/energies.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ class EnergyWidgetBase:
2121
data_key = ""
2222
y_label = "Energy ( kJ / mol )"
2323

24+
_notes = (
25+
"Energies are only available for streaming trajectories and only if the "
26+
"simulation engine is configured to send them."
27+
)
28+
2429
_inputs: ClassVar = [
2530
{
2631
"attribute": "maxlen",

mdadash/backend/kernel/manager.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -589,7 +589,7 @@ async def _get_widget_inputs(self, widget_uuid: str) -> list:
589589
"widget:get_inputs", {"uuid": widget_uuid}
590590
)
591591

592-
async def get_widget_details(self, widget_uuid: str) -> dict:
592+
async def get_widget_details(self, widget_uuid: str) -> dict | None:
593593
"""Get widget details
594594
595595
Get the widget name, description and inputs
@@ -601,13 +601,16 @@ async def get_widget_details(self, widget_uuid: str) -> dict:
601601
602602
Returns
603603
-------
604-
response: dict
605-
Response dict containing name, desc and inputs
604+
response: dict | None
605+
Response dict containing name, desc and inputs or None if
606+
widget does not exist
606607
607608
"""
608609
widget = next(
609610
(w for w in self.sm.widgets_layout if w["i"] == widget_uuid), None
610611
)
612+
if widget is None: # pragma: no cover
613+
return None
611614
response = await self._get_widget_inputs(widget_uuid)
612615
return {
613616
"uuid": widget_uuid,

mdadash/frontend/src/App.vue

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
<template>
22
<v-app>
3+
<span
4+
autofocus
5+
tabindex="-1"
6+
aria-hidden="true"
7+
class="position-absolute"
8+
style="opacity: 0; width: 0; height: 0"
9+
></span>
310
<v-app-bar color="primary" elevation="1" scroll-behavior="hide">
411
<!-- v8 ignore start -->
512
<v-btn
@@ -83,7 +90,7 @@
8390
<template v-slot:activator="{ props }">
8491
<v-btn :icon="mdiDotsVertical" size="large" v-bind="props"></v-btn>
8592
</template>
86-
<v-list>
93+
<v-list class="py-0">
8794
<v-list-item v-for="(item, i) in appBarMenuItems" :key="i" :to="item.path">
8895
<template v-slot:prepend>
8996
<v-icon :icon="item.icon"></v-icon>
@@ -171,14 +178,13 @@ import {
171178
mdiPause,
172179
mdiPlay,
173180
mdiViewDashboard,
174-
mdiViewDashboardOutline,
175181
} from '@mdi/js'
176182
177183
const route = useRoute()
178184
const router = useRouter()
179185
const appBarTitle = computed(() => route.meta.title)
180186
const appBarMenuItems = [
181-
{ name: 'Dashboard', icon: mdiViewDashboardOutline, path: '/' },
187+
{ name: 'Dashboard', icon: mdiViewDashboard, path: '/' },
182188
{ name: 'Notebooks', icon: mdiNotebookMultiple, path: '/notebooks' },
183189
{ name: 'Settings', icon: mdiCog, path: '/settings' },
184190
]

mdadash/frontend/src/__tests__/views/WidgetView.spec.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@ const allProvides = {
1515
settings,
1616
}
1717

18+
const mockPush = vi.fn()
19+
1820
vi.mock('vue-router', () => ({
21+
useRouter: () => ({
22+
push: mockPush,
23+
}),
1924
useRoute: () => ({
2025
query: { uuid: 'uuid1' },
2126
}),
@@ -156,6 +161,7 @@ describe('WidgetView.vue', () => {
156161
const ouputCardItem = outputCard.findComponent({ name: 'VCardItem' })
157162
expect(ouputCardItem).toBeDefined()
158163
await ouputCardItem.trigger('click')
164+
await wrapper.find('form').trigger('submit')
159165
})
160166

161167
it('loads details and output', async () => {

mdadash/frontend/src/views/DashboardView.vue

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@
220220
<v-autocomplete
221221
v-else
222222
:menu="isAddWidgetOpen"
223-
:menu-props="{ maxWidth: '100%' }"
223+
:menu-props="{ maxWidth: '100%', contentClass: 'rounded-t-0' }"
224224
:list-props="{ class: 'py-0' }"
225225
@update:menu="setAddWidgetMenuState"
226226
:items="addWidgetItems"
@@ -231,10 +231,11 @@
231231
ref="addWidgetAutoCompleteRef"
232232
hide-details
233233
variant="solo"
234-
class="border"
234+
class="border flat-bottom"
235235
@update:model-value="onAddWidgetSelected"
236236
:loading="isAddWidgetLoading"
237237
clearable
238+
no-data-text="No matching widgets"
238239
>
239240
<!-- custom template to show both name and description -->
240241
<template #item="{ props, item }">
@@ -669,14 +670,18 @@ onDeactivated(() => {
669670
min-height: 56px !important;
670671
height: 56px !important;
671672
}
672-
673673
.custom-v-select-field-height :deep(.v-field__input) {
674674
min-height: 56px !important;
675675
height: 56px !important;
676676
padding-top: 0px !important;
677677
padding-bottom: 0px !important;
678678
align-items: center;
679679
}
680+
/* Make autocomplete search box have flat bottom */
681+
.flat-bottom :deep(.v-field) {
682+
border-bottom-left-radius: 0px !important;
683+
border-bottom-right-radius: 0px !important;
684+
}
680685
</style>
681686
682687
<style>

mdadash/frontend/src/views/NotebookView.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@
120120
<template v-slot:prepend>
121121
<v-icon :icon="mdiAlert" color="warning"></v-icon>
122122
</template>
123-
<v-card-text> Are you sure you want to delete this Notebook'? </v-card-text>
123+
<v-card-text> Are you sure you want to delete this Notebook? </v-card-text>
124124
<v-card-actions>
125125
<v-spacer></v-spacer>
126126
<v-btn variant="text" @click="confirmDelete = false">Cancel</v-btn>
@@ -252,6 +252,8 @@ onMounted(async () => {
252252
.emitWithAck('notebooks:get_notebook', uuid)
253253
if (response) {
254254
notebook.value = response
255+
} else {
256+
router.push({ path: '/notebooks' })
255257
}
256258
} finally {
257259
isLoading.value = false

mdadash/frontend/src/views/NotebooksView.vue

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
<v-autocomplete
5858
v-else
5959
:menu="isCloneWidgetOpen"
60-
:menu-props="{ maxWidth: '100%' }"
60+
:menu-props="{ maxWidth: '100%', contentClass: 'rounded-t-0' }"
6161
:list-props="{ class: 'py-0' }"
6262
@update:menu="setCloneWidgetMenuState"
6363
:items="cloneWidgetItems"
@@ -68,10 +68,11 @@
6868
ref="cloneWidgetAutoCompleteRef"
6969
hide-details
7070
variant="solo"
71-
class="border"
71+
class="border flat-bottom"
7272
@update:model-value="onCloneWidgetSelected"
7373
:loading="isCloneWidgetLoading"
7474
clearable
75+
no-data-text="No matching widgets"
7576
>
7677
<!-- custom template to show both name and description -->
7778
<template #item="{ props, item }">
@@ -116,7 +117,14 @@
116117
</template>
117118
<!-- Actions -->
118119
<template v-slot:append>
119-
<v-icon :icon="mdiRun" :color="item.run_on_launch ? 'primary' : 'grey'"></v-icon>
120+
<v-icon
121+
:icon="mdiRun"
122+
:color="item.run_on_launch ? 'primary' : 'grey'"
123+
v-tooltip="{
124+
text: item.run_on_launch ? 'Run on launch enabled' : 'Run on launch disabled',
125+
location: 'bottom',
126+
}"
127+
></v-icon>
120128
<v-menu>
121129
<template v-slot:activator="{ props }">
122130
<v-btn
@@ -382,7 +390,13 @@ onBeforeUnmount(() => {
382390
</script>
383391
384392
<style scoped>
393+
/* Bottom border for notebook item except last one */
385394
.notebook-item:not(:last-child) {
386395
border-bottom: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
387396
}
397+
/* Make autocomplete search box have flat bottom */
398+
.flat-bottom :deep(.v-field) {
399+
border-bottom-left-radius: 0px !important;
400+
border-bottom-right-radius: 0px !important;
401+
}
388402
</style>

mdadash/frontend/src/views/WidgetView.vue

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
<v-divider />
5454
<v-expand-transition>
5555
<div v-show="isInputsExpanded">
56-
<v-form class="pa-4">
56+
<v-form class="pa-4" @submit.prevent>
5757
<v-row v-for="input in widgetDetails.inputs" :key="input.attribute">
5858
<v-col cols="12">
5959
<span v-if="input.type === 'toggle'">
@@ -143,13 +143,14 @@
143143

144144
<script setup>
145145
import { socket } from '@/socket'
146-
import { useRoute } from 'vue-router'
146+
import { useRoute, useRouter } from 'vue-router'
147147
import { ref, onMounted, onBeforeUnmount, inject } from 'vue'
148148
import { mdiUnfoldLessHorizontal, mdiUnfoldMoreHorizontal } from '@mdi/js'
149149
import { VTextField, VSelect, VNumberInput, VSwitch, VBtnToggle } from 'vuetify/components'
150150
import NotebookCell from '@/components/NotebookCell.vue'
151151
152152
const route = useRoute()
153+
const router = useRouter()
153154
const uuid = route.query.uuid
154155
const settings = inject('settings')
155156
const isOutputExpanded = ref(true)
@@ -220,6 +221,8 @@ onMounted(async () => {
220221
.emitWithAck('widget:get_details', uuid)
221222
if (response) {
222223
widgetDetails.value = response
224+
} else {
225+
router.push({ path: '/' })
223226
}
224227
} finally {
225228
isLoading.value = false

0 commit comments

Comments
 (0)