Skip to content

Commit 1ecfe08

Browse files
authored
Merge pull request #1775 from MetRonnie/mutation-menu
Simplify mutation menu
2 parents baf5b95 + 975728b commit 1ecfe08

File tree

21 files changed

+127
-202
lines changed

21 files changed

+127
-202
lines changed

src/components/cylc/GraphNode.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
2323
:modifierSize="0.5"
2424
:startTime="startTime"
2525
viewBox="-40 -40 140 140"
26-
v-cylc-object="task"
26+
v-command-menu="task"
2727
x="0" y="0"
2828
/>
2929

@@ -64,7 +64,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
6464
:svg="true"
6565
:status="job.node.state"
6666
viewBox="0 0 100 100"
67-
v-cylc-object="job"
67+
v-command-menu="job"
6868
/>
6969
</g>
7070
<!-- overflow indicator if there are surplus jobs -->

src/components/cylc/cylcObject/Menu.vue renamed to src/components/cylc/commandMenu/Menu.vue

Lines changed: 40 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -18,27 +18,17 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
1818
<template>
1919
<div>
2020
<!-- dropdown menu -->
21-
<component :is="menuTransition" :target="target">
22-
<v-card
23-
ref="menuContent"
24-
v-if="node"
25-
v-show="showMenu"
26-
@show-mutations-menu="showMutationsMenu"
27-
:key="node.id"
28-
v-click-outside="{ handler: onClickOutside, closeConditional: () => !dialog }"
29-
class="c-mutation-menu elevation-10 overflow-y-auto"
30-
max-height="90vh"
31-
width="max-content"
32-
max-width="min(600px, 100%)"
33-
theme="dark"
34-
position="absolute"
35-
:style="{
36-
left: `${x}px`,
37-
top: `${y}px`,
38-
// Set transition origin relative to clicked target:
39-
'--v-overlay-anchor-origin': 'bottom right',
40-
}"
41-
>
21+
<v-menu
22+
v-if="node"
23+
:key="target.dataset.cInteractive"
24+
v-model="showMenu"
25+
:target="target"
26+
:close-on-content-click="false"
27+
content-class="c-mutation-menu"
28+
max-width="600px"
29+
theme="dark"
30+
>
31+
<v-card>
4232
<v-card-title class="pb-1 pt-3">
4333
{{ node.id }}
4434
</v-card-title>
@@ -89,7 +79,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
8979
<v-list-item v-if="canExpand">
9080
<v-btn
9181
id="less-more-button"
92-
@click="expandCollapse"
82+
@click="() => expanded = !expanded"
9383
block
9484
variant="tonal"
9585
>
@@ -98,7 +88,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
9888
</v-list-item>
9989
</v-list>
10090
</v-card>
101-
</component>
91+
</v-menu>
10292
<v-dialog
10393
v-if="dialogMutation"
10494
v-model="dialog"
@@ -111,7 +101,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
111101
:cylcObject="node"
112102
:initialData="initialData(dialogMutation, node.tokens)"
113103
@close="() => dialog = false"
114-
@success="closeMenu"
104+
@success="() => showMenu = false"
115105
:types="types"
116106
:key="dialogKey /* Enables re-render of component each time dialog opened */"
117107
ref="mutationComponent"
@@ -121,23 +111,21 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
121111
</template>
122112

123113
<script>
124-
import { computed, nextTick } from 'vue'
114+
import { nextTick } from 'vue'
125115
import {
126116
filterAssociations,
127117
getMutationArgsFromTokens,
128118
mutate
129119
} from '@/utils/aotf'
130-
import { useReducedAnimation } from '@/composables/localStorage'
131120
import Mutation from '@/components/cylc/Mutation.vue'
132121
import {
133122
mdiPencil
134123
} from '@mdi/js'
135124
import { mapGetters, mapState } from 'vuex'
136125
import WorkflowState from '@/model/WorkflowState.model'
137-
import { VDialogTransition } from 'vuetify/components/transitions'
138126
139127
export default {
140-
name: 'CylcObjectMenu',
128+
name: 'CommandMenu',
141129
142130
components: {
143131
Mutation
@@ -151,15 +139,6 @@ export default {
151139
}
152140
},
153141
154-
setup () {
155-
const reducedAnimation = useReducedAnimation()
156-
return {
157-
menuTransition: computed(
158-
() => reducedAnimation.value ? 'slot' : VDialogTransition
159-
),
160-
}
161-
},
162-
163142
data () {
164143
return {
165144
dialog: false,
@@ -171,20 +150,16 @@ export default {
171150
isLoadingMutations: true,
172151
showMenu: false,
173152
types: [],
174-
x: 0,
175-
y: 0,
176153
target: null,
177154
}
178155
},
179156
180157
mounted () {
181158
this.$eventBus.on('show-mutations-menu', this.showMutationsMenu)
182-
document.addEventListener('keydown', this.onKeydown)
183159
},
184160
185161
beforeUnmount () {
186162
this.$eventBus.off('show-mutations-menu', this.showMutationsMenu)
187-
document.removeEventListener('keydown', this.onKeydown)
188163
},
189164
190165
computed: {
@@ -268,66 +243,6 @@ export default {
268243
this.dialogKey = !this.dialogKey
269244
},
270245
271-
closeMenu () {
272-
this.showMenu = false
273-
this.expanded = false
274-
},
275-
276-
/**
277-
* Handler for clicking outside menu (close it).
278-
*
279-
* We override vuetify default handling because we don't want to
280-
* close when clicking on another cylc object.
281-
*
282-
* @param {Event} e - the click event
283-
*/
284-
onClickOutside (e) {
285-
this.closeMenu()
286-
287-
// check if the thing being clicked on is a child of the thing that the
288-
// menu is open for
289-
let target = e.target
290-
while (target) {
291-
if (target?.getAttribute('data-c-interactive')) {
292-
// keep the menu open
293-
this.showMenu = true
294-
break
295-
}
296-
target = target.parentElement
297-
}
298-
},
299-
300-
onKeydown (e) {
301-
if (!this.dialog && e.key === 'Escape') {
302-
this.closeMenu()
303-
}
304-
},
305-
306-
expandCollapse () {
307-
this.expanded = !this.expanded
308-
this.reposition()
309-
},
310-
311-
/**
312-
* Place the menu in a way that prevents it overflowing off screen and
313-
* so it takes up the full width as needed on narrow viewports.
314-
*
315-
* @param {number} x - preferred x coordinate
316-
* @param {number} y - preferred y coordinate
317-
*/
318-
reposition (x = null, y = null) {
319-
x ??= this.x
320-
y ??= this.y
321-
nextTick(() => {
322-
this.x = x + this.$refs.menuContent.$el.clientWidth > document.body.clientWidth
323-
? document.body.clientWidth - this.$refs.menuContent.$el.clientWidth
324-
: x
325-
this.y = y + this.$refs.menuContent.$el.clientHeight > document.body.clientHeight
326-
? document.body.clientHeight - this.$refs.menuContent.$el.clientHeight - 5
327-
: y
328-
})
329-
},
330-
331246
/* Call a mutation using only the tokens for args. */
332247
callMutationFromContext (mutation) {
333248
this.showMenu = false
@@ -362,32 +277,32 @@ export default {
362277
}
363278
},
364279
365-
showMutationsMenu ({ node, event }) {
366-
this.target = event.target
280+
async showMutationsMenu ({ node, target }) {
281+
this.target = target
367282
this.node = node
283+
this.expanded = false
284+
// show the menu after it's rendered to ensure animation works properly
285+
await nextTick()
368286
this.showMenu = true
369-
this.reposition(event.clientX, event.clientY)
370-
// await graphql query to get mutations
371-
this.$workflowService.introspection.then(({ mutations, types }) => {
372-
// if mutations are slow to load then there will be a delay before they are reactively
373-
// displayed in the menu (this is what the skeleton-loader is for)
374-
this.isLoadingMutations = false
375-
this.types = types
376-
let type = this.node.type
377-
if (type === 'family') {
378-
// show the same mutation list for families as for tasks
379-
type = 'task'
380-
}
381-
this.mutations = filterAssociations(
382-
type,
383-
this.node.tokens,
384-
mutations,
385-
this.user.permissions
386-
).sort(
387-
(a, b) => a.mutation.name.localeCompare(b.mutation.name)
388-
)
389-
this.reposition(event.clientX, event.clientY)
390-
})
287+
// ensure graphql query to get mutations has completed
288+
const { mutations, types } = await this.$workflowService.introspection
289+
// if mutations are slow to load then there will be a delay before they are reactively
290+
// displayed in the menu (this is what the skeleton-loader is for)
291+
this.isLoadingMutations = false
292+
this.types = types
293+
let type = this.node.type
294+
if (type === 'family') {
295+
// show the same mutation list for families as for tasks
296+
type = 'task'
297+
}
298+
this.mutations = filterAssociations(
299+
type,
300+
this.node.tokens,
301+
mutations,
302+
this.user.permissions
303+
).sort(
304+
(a, b) => a.mutation.name.localeCompare(b.mutation.name)
305+
)
391306
},
392307
393308
initialData (mutation, tokens) {

src/components/cylc/cylcObject/plugin.js renamed to src/components/cylc/commandMenu/plugin.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/**
1+
/*
22
* Copyright (C) NIWA & British Crown (Met Office) & Contributors.
33
*
44
* This program is free software: you can redistribute it and/or modify
@@ -15,19 +15,23 @@
1515
* along with this program. If not, see <http://www.gnu.org/licenses/>.
1616
*/
1717

18-
// reference to closure listeners (needed as we are using variables from another scope)
18+
import { uniqueId } from 'lodash-es'
19+
20+
/** Reference to closure listeners (needed as we are using variables from another scope) */
1921
const listeners = new WeakMap()
2022

2123
function bind (el, binding, vnode) {
24+
// Set data-c-interactive = <unique identifier> for every element that opens the menu.
25+
// This allows the menu component to tell apart every activating element.
26+
el.dataset.cInteractive = uniqueId()
2227
const listener = function (e) {
2328
e.stopPropagation() // prevents click event from bubbling up to parents
2429
binding.instance.$eventBus.emit('show-mutations-menu', {
2530
node: binding.value,
26-
event: e
31+
target: el,
2732
})
2833
}
2934
el.addEventListener('click', listener)
30-
el.dataset.cInteractive = true
3135
listeners.set(el, listener)
3236
}
3337

@@ -55,7 +59,7 @@ export default {
5559
*/
5660
install (app, options) {
5761
// add a global directive
58-
app.directive('cylc-object', {
62+
app.directive('command-menu', {
5963
beforeMount: bind,
6064
unmounted: unbind,
6165
updated

src/components/cylc/table/Table.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
3131
<div class="d-flex align-content-center flex-nowrap">
3232
<div style="width: 2em;">
3333
<Task
34-
v-cylc-object="item.task"
34+
v-command-menu="item.task"
3535
:task="item.task.node"
3636
:startTime="item.latestJob?.node?.startedTime"
3737
/>
3838
</div>
3939
<div style="width: 2em;">
4040
<Job
4141
v-if="item.latestJob"
42-
v-cylc-object="item.latestJob"
42+
v-command-menu="item.latestJob"
4343
:status="item.latestJob.node.state"
4444
:previous-state="item.previousJob?.node?.state"
4545
/>
@@ -77,7 +77,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
7777
<div class="d-flex align-content-center flex-nowrap">
7878
<div class="d-flex" style="margin-left: 2em;">
7979
<Job
80-
v-cylc-object="job"
80+
v-command-menu="job"
8181
:key="`${job.id}-summary-${index}`"
8282
:status="job.node.state"
8383
/>

src/components/cylc/tree/GScanTreeItem.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
2525
<WorkflowIcon
2626
v-if="node.type === 'workflow'"
2727
:status="node.node.status"
28-
v-cylc-object="node"
28+
v-command-menu="node"
2929
:class="nodeClass"
3030
class="flex-shrink-0"
3131
/>

src/components/cylc/tree/TreeItem.vue

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
4545
<template v-if="node.type === 'cycle'">
4646
<!-- NOTE: cycle point nodes don't have any data associated with them
4747
at present so we must use the root family node for the task icon.
48-
We don't use this for the v-cylc-object as that would set the node
48+
We don't use this for the v-command-menu as that would set the node
4949
type to family. -->
5050
<Task
51-
v-cylc-object="node"
51+
v-command-menu="node"
5252
v-if="node.familyTree?.length"
5353
:key="node.id"
5454
:task="node.familyTree[0].node"
@@ -57,7 +57,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
5757
</template>
5858
<template v-else-if="node.type === 'family'">
5959
<Task
60-
v-cylc-object="node"
60+
v-command-menu="node"
6161
:key="node.id"
6262
:task="node.node"
6363
/>
@@ -66,7 +66,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
6666
<template v-else-if="node.type === 'task'">
6767
<!-- Task summary -->
6868
<Task
69-
v-cylc-object="node"
69+
v-command-menu="node"
7070
:key="node.id"
7171
:task="node.node"
7272
:startTime="latestJob(node)?.startedTime"
@@ -78,7 +78,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
7878
<!-- most recent job summary -->
7979
<Job
8080
v-for="(job, index) in node.children.slice(0, 1)"
81-
v-cylc-object="job"
81+
v-command-menu="job"
8282
:key="`${job.id}-summary-${index}`"
8383
:status="job.node.state"
8484
:previous-state="node.children.length > 1 ? node.children[1].node.state : ''"
@@ -88,7 +88,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
8888
</template>
8989
<template v-else-if="node.type === 'job'">
9090
<Job
91-
v-cylc-object="node"
91+
v-command-menu="node"
9292
:key="node.id"
9393
:status="node.node.state"
9494
/>

0 commit comments

Comments
 (0)