@@ -30,24 +30,83 @@ 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+ class =" input"
43+ v-bind =" iControl.props"
44+ clearable
45+ :prepend-inner-icon =" iControl.icon"
46+ @update:modelValue =" iControl.callback"
47+ @focus =" autoResizeInput"
48+ @blur =" autoResizeInput"
49+ />
50+
51+ <!-- buttons -->
52+ <v-btn
53+ v-else
54+ class =" control-btn"
55+ v-bind =" {...$attrs, ...props, ...btnProps}"
56+ @click =" (e) => {iControl.action === 'menu' ? null : iControl.callback(e)}"
57+ :disabled =" iControl.disabled"
58+ :aria-checked =" iControl.value"
59+ :color =" isSet(iControl.value) ? 'blue' : undefined"
60+ role =" switch"
61+ density =" compact"
62+ >
63+ <v-icon >{{ iControl.icon[iControl.value] || iControl.icon }}</v-icon >
64+ <v-tooltip >{{ iControl.title }}</v-tooltip >
65+ </v-btn >
66+ </template >
67+
68+ <!-- dropdowns -->
69+ <v-treeview
70+ v-if =" iControl.action === 'menu'"
71+ v-bind =" iControl.props"
72+ @update:activated =" iControl.callback"
73+ color =" blue"
74+ density =" compact"
75+ >
76+ <!-- task icons (for task state filters) -->
77+ <template
78+ v-slot :prepend =" { item } "
79+ v-if =" iControl .props [' task-state-icons' ]"
80+ >
81+ <Task :task =" item.props" />
82+ </template >
83+ <!-- disable expansion until parent active (for task state filters) -->
84+ <template
85+ v-slot :toggle =" { props: toggleProps , isActive , isOpen } "
86+ v-if =" iControl .props [' task-state-icons' ]"
87+ >
88+ <v-icon
89+ :icon =" isOpen ? $options.icons.mdiChevronUp : $options.icons.mdiChevronDown"
90+ :disabled =" !isActive && !isOpen"
91+ v-bind =" toggleProps"
92+ />
93+ </template >
94+ </v-treeview >
95+ </v-menu >
4496 </div >
4597 </div >
4698 </div >
4799</template >
48100
49101<script >
50- import { btnProps } from ' @/utils/viewToolbar'
102+ import { btnProps , taskStateItems } from ' @/utils/viewToolbar'
103+ import Task from ' @/components/cylc/Task.vue'
104+ import {
105+ mdiChevronDown ,
106+ mdiChevronUp ,
107+ mdiFilter ,
108+ mdiMagnify ,
109+ } from ' @mdi/js'
51110
52111export default {
53112 name: ' ViewToolbar' ,
@@ -56,10 +115,20 @@ export default {
56115 ' setOption'
57116 ],
58117
118+ components: {
119+ Task
120+ },
121+
122+ icons: {
123+ mdiChevronDown,
124+ mdiChevronUp,
125+ mdiMagnify,
126+ },
127+
59128 props: {
60129 groups: {
61130 required: true ,
62- type: Array
131+ type: Array ,
63132 /*
64133 groups: [
65134 {
@@ -70,21 +139,40 @@ export default {
70139 {
71140 // display name
72141 title: String,
142+
73143 // unique key:
74144 // * Provided with "setOption" events.
75145 // * Used by enableIf/disableIf
76146 // * Added to the control's class list for testing.
77147 key: String
148+
149+ // icon for the control:
150+ // * Either an icon.
151+ // * Or a mapping of state to an icon.
152+ // NOTE: this is autopopulated for action="taskStateFilter | taskIDFilter"
153+ icon: Icon | Object[key, Icon]
154+
78155 // action to perform when clicked:
156+ // Generic actions:
79157 // * toggle - toggle true/false
80158 // * callback - call the provided callback
159+ // * menu - open a menu (provide props: {items} in v-treeview format)
160+ // Specialised actions:
161+ // * taskIDFilter - Search box for task IDs
162+ // * taskStateFilter - open a task state filter menu
81163 action: String
164+
82165 // for use with action='callback'
83166 callback: Fuction
167+
168+ // props to be set on the control
169+ props: Object
170+
84171 // list of keys
85172 // only enable this control if all of the listed controls have
86173 // truthy values
87174 enableIf
175+
88176 // list of keys
89177 // disable this control if any of the listed controls have
90178 // truthy values
@@ -111,6 +199,7 @@ export default {
111199 let iControl
112200 let callback // callback to fire when control is activated
113201 let disabled // true if control should not be enabled
202+ let props
114203 const values = this .getValues ()
115204 for (const group of this .groups ) {
116205 iGroup = {
@@ -120,15 +209,43 @@ export default {
120209 for (const control of group .controls ) {
121210 callback = null
122211 disabled = false
212+ props = control .props || {}
123213
124214 // set callback
125215 switch (control .action ) {
126- case ' toggle' :
216+ case ' toggle' : // toggle button
127217 callback = (e ) => this .toggle (control, e)
128218 break
129- case ' callback' :
219+ case ' callback' : // button which actions a callback
130220 callback = (e ) => this .call (control, e)
131221 break
222+ case ' taskIDFilter' : // specialised "input" for filtering tasks
223+ callback = (value ) => this .set (control, value)
224+ control .icon = mdiMagnify
225+ control .action = ' input'
226+ props = {
227+ placeholder: ' Search' ,
228+ ... props,
229+ }
230+ break
231+ case ' input' : // text input
232+ callback = (value ) => this .set (control, value)
233+ break
234+ case ' taskStateFilter' : // specialised "menu" for filtering tasks
235+ control .action = ' menu'
236+ control .icon = mdiFilter
237+ props = {
238+ items: taskStateItems,
239+ ' indent-lines' : true ,
240+ activatable: true ,
241+ ' active-strategy' : ' independent' ,
242+ ' item-value' : ' value' ,
243+ ' task-state-icons' : true , // flag to enable special slots
244+ ... props,
245+
246+ }
247+ callback = (value ) => this .set (control, value)
248+ break
132249 }
133250
134251 // set disabled
@@ -147,6 +264,7 @@ export default {
147264
148265 iControl = {
149266 ... control,
267+ props,
150268 callback,
151269 disabled
152270 }
@@ -186,6 +304,29 @@ export default {
186304 }
187305 return vars
188306 },
307+ set (control, value) {
308+ // update the value
309+ this .$emit (' setOption' , control .key , value)
310+ },
311+ autoResizeInput (e ) {
312+ // enlarge a text input when focused or containing text
313+ if (e .type === ' focus' ) {
314+ e .target .classList .add (' expanded' )
315+ } else {
316+ if (e .target .value ) {
317+ e .target .classList .add (' expanded' )
318+ } else {
319+ e .target .classList .remove (' expanded' )
320+ }
321+ }
322+ },
323+ isSet (value ) {
324+ // determine if a control is active or not
325+ if (Array .isArray (value)) {
326+ return value .length
327+ }
328+ return value
329+ }
189330 }
190331}
191332 </script >
@@ -204,11 +345,25 @@ export default {
204345 // place a divider between groups
205346 content : ' ' ;
206347 height : 70% ;
207- width : 2px ;
208- background : rgb (0 , 0 , 0 , 0.22 );
348+ width : 0.15em ;
349+ border-radius : 0.15em ;
350+ background : rgb (0 , 0 , 0 , 0.18 );
209351 // put a bit of space between the groups
210352 margin : 0 $spacing ;
211353 }
354+
355+ // pack buttons more tightly than the vuetify default
356+ .control-btn {
357+ margin : 0.4em 0.25em 0.4em 0.25em ;
358+ }
359+
360+ // auto expand/collapse the search bar
361+ .input {
362+ width : 8em ;
363+ }
364+ .input :has (input .expanded ) {
365+ width : 20em ;
366+ }
212367 }
213368 }
214369 </style >
0 commit comments