@@ -30,24 +30,90 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
3030 class =" control"
3131 :data-cy =" `control-${iControl.key}`"
3232 >
33- <v-btn
34- @click =" iControl.callback"
35- v-bind =" btnProps"
36- :disabled =" iControl.disabled"
37- :aria-checked =" iControl.value"
38- :color =" iControl.value ? 'blue' : undefined"
39- role =" switch"
33+ <!-- menu component (to support dropdowns) -->
34+ <v-menu
35+ eager
36+ :close-on-content-click =" false"
4037 >
41- <v-icon >{{ iControl.icon }}</v-icon >
42- <v-tooltip >{{ iControl.title }}</v-tooltip >
43- </v-btn >
38+ <template v-slot :activator =" { props } " >
39+ <!-- inputs -->
40+ <v-text-field
41+ v-if =" iControl.action === 'input'"
42+ v-model =" iControl.value"
43+ class =" input"
44+ v-bind =" iControl.props"
45+ clearable
46+ :prepend-inner-icon =" iControl.icon"
47+ @update:modelValue =" iControl.callback"
48+ @focus =" autoResizeInput"
49+ @blur =" autoResizeInput"
50+ />
51+
52+ <!-- buttons -->
53+ <v-btn
54+ v-else
55+ class =" control-btn"
56+ v-bind =" {...$attrs, ...props, ...btnProps}"
57+ @click =" (e) => {iControl.action === 'menu' ? null : iControl.callback(e)}"
58+ :disabled =" iControl.disabled"
59+ :aria-checked =" iControl.value"
60+ :color =" isSet(iControl.value) ? 'blue' : undefined"
61+ role =" switch"
62+ density =" compact"
63+ >
64+ <v-icon >{{ iControl.icon[iControl.value] || iControl.icon }}</v-icon >
65+ <v-tooltip >{{ iControl.title }}</v-tooltip >
66+ </v-btn >
67+ </template >
68+
69+ <!-- dropdowns -->
70+ <v-card
71+ v-if =" iControl.action === 'menu'"
72+ >
73+ <v-btn
74+ :prepend-icon =" $options.icons.mdiUndo"
75+ variant =" plain"
76+ @click =" iControl.callback([])"
77+ block
78+ spaced =" end"
79+ :data-cy =" `control-${iControl.key}-reset`"
80+ >
81+ Reset
82+ </v-btn >
83+ <v-divider ></v-divider >
84+
85+ <v-treeview
86+ v-bind =" iControl.props"
87+ v-model:activated =" iControl.value"
88+ @update:activated =" iControl.callback"
89+ color =" blue"
90+ density =" compact"
91+ style =" padding-top : 0 ;"
92+ >
93+ <!-- task icons (for task state filters -->
94+ <template
95+ v-slot :prepend =" { item } "
96+ v-if =" iControl .props [' task-state-icons' ]"
97+ >
98+ <Task :task =" item.taskProps" />
99+ </template >
100+ </v-treeview >
101+ </v-card >
102+ </v-menu >
44103 </div >
45104 </div >
46105 </div >
47106</template >
48107
49108<script >
50- import { btnProps } from ' @/utils/viewToolbar'
109+ import { btnProps , taskStateItems } from ' @/utils/viewToolbar'
110+ import Task from ' @/components/cylc/Task.vue'
111+ import {
112+ mdiFilter ,
113+ mdiMagnify ,
114+ mdiUndo ,
115+ } from ' @mdi/js'
116+ import { TaskState , WaitingStateModifierNames } from ' @/model/TaskState.model'
51117
52118export default {
53119 name: ' ViewToolbar' ,
@@ -56,10 +122,18 @@ export default {
56122 ' setOption'
57123 ],
58124
125+ components: {
126+ Task
127+ },
128+
129+ icons: {
130+ mdiUndo,
131+ },
132+
59133 props: {
60134 groups: {
61135 required: true ,
62- type: Array
136+ type: Array ,
63137 /*
64138 groups: [
65139 {
@@ -70,21 +144,40 @@ export default {
70144 {
71145 // display name
72146 title: String,
147+
73148 // unique key:
74149 // * Provided with "setOption" events.
75150 // * Used by enableIf/disableIf
76151 // * Added to the control's class list for testing.
77152 key: String
153+
154+ // icon for the control:
155+ // * Either an icon.
156+ // * Or a mapping of state to an icon.
157+ // NOTE: this is autopopulated for action="taskStateFilter | taskIDFilter"
158+ icon: Icon | Object[key, Icon]
159+
78160 // action to perform when clicked:
161+ // Generic actions:
79162 // * toggle - toggle true/false
80163 // * callback - call the provided callback
164+ // * menu - open a menu (provide props: {items} in v-treeview format)
165+ // Specialised actions:
166+ // * taskIDFilter - Search box for task IDs
167+ // * taskStateFilter - open a task state filter menu
81168 action: String
169+
82170 // for use with action='callback'
83171 callback: Fuction
172+
173+ // props to be set on the control
174+ props: Object
175+
84176 // list of keys
85177 // only enable this control if all of the listed controls have
86178 // truthy values
87179 enableIf
180+
88181 // list of keys
89182 // disable this control if any of the listed controls have
90183 // truthy values
@@ -111,6 +204,8 @@ export default {
111204 let iControl
112205 let callback // callback to fire when control is activated
113206 let disabled // true if control should not be enabled
207+ let props
208+ let action
114209 const values = this .getValues ()
115210 for (const group of this .groups ) {
116211 iGroup = {
@@ -120,15 +215,43 @@ export default {
120215 for (const control of group .controls ) {
121216 callback = null
122217 disabled = false
218+ props = control .props || {}
219+ action = control .action
123220
124221 // set callback
125- switch (control . action ) {
126- case ' toggle' :
222+ switch (action) {
223+ case ' toggle' : // toggle button
127224 callback = (e ) => this .toggle (control, e)
128225 break
129- case ' callback' :
226+ case ' callback' : // button which actions a callback
130227 callback = (e ) => this .call (control, e)
131228 break
229+ case ' taskIDFilter' : // specialised "input" for filtering tasks
230+ callback = (value ) => this .set (control, value)
231+ control .icon = mdiMagnify
232+ action = ' input'
233+ props = {
234+ placeholder: ' Search (globs supported)' ,
235+ ... props,
236+ }
237+ break
238+ case ' input' : // text input
239+ callback = (value ) => this .set (control, value)
240+ break
241+ case ' taskStateFilter' : // specialised "menu" for filtering tasks
242+ action = ' menu'
243+ control .icon = mdiFilter
244+ props = {
245+ items: taskStateItems,
246+ ' indent-lines' : true ,
247+ activatable: true ,
248+ ' active-strategy' : ' independent' ,
249+ ' item-value' : ' value' ,
250+ ' task-state-icons' : true , // flag to enable special slots
251+ ... props,
252+ }
253+ callback = (value ) => this .set (control, value)
254+ break
132255 }
133256
134257 // set disabled
@@ -147,6 +270,8 @@ export default {
147270
148271 iControl = {
149272 ... control,
273+ action,
274+ props,
150275 callback,
151276 disabled
152277 }
@@ -186,6 +311,40 @@ export default {
186311 }
187312 return vars
188313 },
314+ set (control, value) {
315+ // update the value
316+ if ( // special logic for the taskStateFilter
317+ control .action === ' taskStateFilter' &&
318+ // if a waiting state modifier is selected
319+ value .some ((modifier ) => WaitingStateModifierNames .includes (modifier)) &&
320+ // but the waiting state is not
321+ ! value .includes (TaskState .WAITING .name )
322+ ) {
323+ // then add the waiting state (i.e, don't allow the user to de-select
324+ // waiting whilst a modifier is in play)
325+ value .push (TaskState .WAITING .name )
326+ }
327+ this .$emit (' setOption' , control .key , value)
328+ },
329+ autoResizeInput (e ) {
330+ // enlarge a text input when focused or containing text
331+ if (e .type === ' focus' ) {
332+ e .target .classList .add (' expanded' )
333+ } else {
334+ if (e .target .value ) {
335+ e .target .classList .add (' expanded' )
336+ } else {
337+ e .target .classList .remove (' expanded' )
338+ }
339+ }
340+ },
341+ isSet (value ) {
342+ // determine if a control is active or not
343+ if (Array .isArray (value)) {
344+ return value .length
345+ }
346+ return value
347+ }
189348 }
190349}
191350 </script >
@@ -204,11 +363,25 @@ export default {
204363 // place a divider between groups
205364 content : ' ' ;
206365 height : 70% ;
207- width : 2px ;
208- background : rgb (0 , 0 , 0 , 0.22 );
366+ width : 0.15em ;
367+ border-radius : 0.15em ;
368+ background : rgb (0 , 0 , 0 , 0.18 );
209369 // put a bit of space between the groups
210370 margin : 0 $spacing ;
211371 }
372+
373+ // pack buttons more tightly than the vuetify default
374+ .control-btn {
375+ margin : 0.4em 0.25em 0.4em 0.25em ;
376+ }
377+
378+ // auto expand/collapse the search bar
379+ .input {
380+ width : 8em ;
381+ }
382+ .input :has (input .expanded ) {
383+ width : 20em ;
384+ }
212385 }
213386 }
214387 </style >
0 commit comments