]>
Commit | Line | Data |
---|---|---|
1b42bb86 DC |
1 | Ext.define('PVE.panel.TagEditContainer', { |
2 | extend: 'Ext.container.Container', | |
3 | alias: 'widget.pveTagEditContainer', | |
4 | ||
5 | layout: { | |
6 | type: 'hbox', | |
59e71a08 | 7 | align: 'middle', |
1b42bb86 DC |
8 | }, |
9 | ||
1b48b8b7 DC |
10 | // set to false to hide the 'no tags' field and the edit button |
11 | canEdit: true, | |
12 | ||
1b42bb86 DC |
13 | controller: { |
14 | xclass: 'Ext.app.ViewController', | |
15 | ||
16 | loadTags: function(tagstring = '', force = false) { | |
17 | let me = this; | |
18 | let view = me.getView(); | |
19 | ||
20 | if (me.oldTags === tagstring && !force) { | |
21 | return; | |
22 | } | |
23 | ||
24 | view.suspendLayout = true; | |
25 | me.forEachTag((tag) => { | |
26 | view.remove(tag); | |
27 | }); | |
28 | me.getViewModel().set('tagCount', 0); | |
29 | let newtags = tagstring.split(/[;, ]/).filter((t) => !!t) || []; | |
30 | newtags.forEach((tag) => { | |
31 | me.addTag(tag); | |
32 | }); | |
33 | view.suspendLayout = false; | |
34 | view.updateLayout(); | |
35 | if (!force) { | |
36 | me.oldTags = tagstring; | |
37 | } | |
66dace4b | 38 | me.tagsChanged(); |
1b42bb86 DC |
39 | }, |
40 | ||
41 | onRender: function(v) { | |
42 | let me = this; | |
43 | let view = me.getView(); | |
731436ee | 44 | view.toggleCls('hide-handles', PVE.UIOptions.shouldSortTags()); |
8d8ba23d | 45 | |
1b42bb86 DC |
46 | view.dragzone = Ext.create('Ext.dd.DragZone', v.getEl(), { |
47 | getDragData: function(e) { | |
48 | let source = e.getTarget('.handle'); | |
49 | if (!source) { | |
50 | return undefined; | |
51 | } | |
52 | let sourceId = source.parentNode.id; | |
53 | let cmp = Ext.getCmp(sourceId); | |
54 | let ddel = document.createElement('div'); | |
55 | ddel.classList.add('proxmox-tags-full'); | |
731436ee | 56 | ddel.innerHTML = Proxmox.Utils.getTagElement(cmp.tag, PVE.UIOptions.tagOverrides); |
1b42bb86 DC |
57 | let repairXY = Ext.fly(source).getXY(); |
58 | cmp.setDisabled(true); | |
59 | ddel.id = Ext.id(); | |
60 | return { | |
61 | ddel, | |
62 | repairXY, | |
63 | sourceId, | |
64 | }; | |
65 | }, | |
66 | onMouseUp: function(target, e, id) { | |
67 | let cmp = Ext.getCmp(this.dragData.sourceId); | |
68 | if (cmp && !cmp.isDestroyed) { | |
69 | cmp.setDisabled(false); | |
70 | } | |
71 | }, | |
72 | getRepairXY: function() { | |
73 | return this.dragData.repairXY; | |
74 | }, | |
75 | beforeInvalidDrop: function(target, e, id) { | |
76 | let cmp = Ext.getCmp(this.dragData.sourceId); | |
77 | if (cmp && !cmp.isDestroyed) { | |
78 | cmp.setDisabled(false); | |
79 | } | |
80 | }, | |
81 | }); | |
82 | view.dropzone = Ext.create('Ext.dd.DropZone', v.getEl(), { | |
83 | getTargetFromEvent: function(e) { | |
84 | return e.getTarget('.proxmox-tag-dark,.proxmox-tag-light'); | |
85 | }, | |
86 | getIndicator: function() { | |
87 | if (!view.indicator) { | |
88 | view.indicator = Ext.create('Ext.Component', { | |
89 | floating: true, | |
90 | html: '<i class="fa fa-long-arrow-up"></i>', | |
91 | hidden: true, | |
92 | shadow: false, | |
93 | }); | |
94 | } | |
95 | return view.indicator; | |
96 | }, | |
97 | onContainerOver: function() { | |
98 | this.getIndicator().setVisible(false); | |
99 | }, | |
100 | notifyOut: function() { | |
101 | this.getIndicator().setVisible(false); | |
102 | }, | |
103 | onNodeOver: function(target, dd, e, data) { | |
104 | let indicator = this.getIndicator(); | |
105 | indicator.setVisible(true); | |
106 | indicator.alignTo(Ext.getCmp(target.id), 't50-bl', [-1, -2]); | |
107 | return this.dropAllowed; | |
108 | }, | |
109 | onNodeDrop: function(target, dd, e, data) { | |
110 | this.getIndicator().setVisible(false); | |
111 | let sourceCmp = Ext.getCmp(data.sourceId); | |
112 | if (!sourceCmp) { | |
113 | return; | |
114 | } | |
115 | sourceCmp.setDisabled(false); | |
116 | let targetCmp = Ext.getCmp(target.id); | |
117 | view.remove(sourceCmp, { destroy: false }); | |
118 | view.insert(view.items.indexOf(targetCmp), sourceCmp); | |
66dace4b | 119 | me.tagsChanged(); |
1b42bb86 DC |
120 | }, |
121 | }); | |
122 | }, | |
123 | ||
124 | forEachTag: function(func) { | |
125 | let me = this; | |
126 | let view = me.getView(); | |
127 | view.items.each((field) => { | |
1b42bb86 DC |
128 | if (field.getXType() === 'pveTag') { |
129 | func(field); | |
130 | } | |
131 | return true; | |
132 | }); | |
133 | }, | |
134 | ||
135 | toggleEdit: function(cancel) { | |
136 | let me = this; | |
137 | let vm = me.getViewModel(); | |
59e71a08 | 138 | let view = me.getView(); |
1b42bb86 DC |
139 | let editMode = !vm.get('editMode'); |
140 | vm.set('editMode', editMode); | |
141 | ||
142 | // get a current tag list for editing | |
143 | if (editMode) { | |
731436ee | 144 | PVE.UIOptions.update(); |
1b42bb86 DC |
145 | } |
146 | ||
147 | me.forEachTag((tag) => { | |
148 | tag.setMode(editMode ? 'editable' : 'normal'); | |
149 | }); | |
150 | ||
151 | if (!vm.get('editMode')) { | |
152 | let tags = []; | |
153 | if (cancel) { | |
154 | me.loadTags(me.oldTags, true); | |
155 | } else { | |
59e71a08 | 156 | let toRemove = []; |
1b42bb86 DC |
157 | me.forEachTag((cmp) => { |
158 | if (cmp.isVisible() && cmp.tag) { | |
159 | tags.push(cmp.tag); | |
59e71a08 DC |
160 | } else { |
161 | toRemove.push(cmp); | |
1b42bb86 DC |
162 | } |
163 | }); | |
59e71a08 | 164 | toRemove.forEach(cmp => view.remove(cmp)); |
1b42bb86 DC |
165 | tags = tags.join(','); |
166 | if (me.oldTags !== tags) { | |
167 | me.oldTags = tags; | |
59e71a08 | 168 | me.loadTags(tags, true); |
1b42bb86 DC |
169 | me.getView().fireEvent('change', tags); |
170 | } | |
171 | } | |
172 | } | |
173 | me.getView().updateLayout(); | |
174 | }, | |
175 | ||
66dace4b | 176 | tagsChanged: function() { |
38f4e6e4 DC |
177 | let me = this; |
178 | let tags = []; | |
179 | me.forEachTag(cmp => { | |
180 | if (cmp.tag) { | |
181 | tags.push(cmp.tag); | |
182 | } | |
183 | }); | |
66dace4b | 184 | me.getViewModel().set('isDirty', me.oldTags !== tags.join(',')); |
38f4e6e4 DC |
185 | me.forEachTag(cmp => { |
186 | cmp.updateFilter(tags); | |
187 | }); | |
188 | }, | |
189 | ||
59e71a08 | 190 | addTag: function(tag, isNew) { |
1b42bb86 DC |
191 | let me = this; |
192 | let view = me.getView(); | |
193 | let vm = me.getViewModel(); | |
59e71a08 | 194 | let index = view.items.length - 5; |
731436ee | 195 | if (PVE.UIOptions.shouldSortTags() && !isNew) { |
8d8ba23d | 196 | index = view.items.findIndexBy(tagField => { |
59e71a08 DC |
197 | if (tagField.reference === 'noTagsField') { |
198 | return false; | |
199 | } | |
200 | if (tagField.xtype !== 'pveTag') { | |
8d8ba23d DC |
201 | return true; |
202 | } | |
13a0c8bf | 203 | let a = tagField.tag.toLowerCase(); |
871953db DC |
204 | let b = tag.toLowerCase(); |
205 | return a > b ? true : a < b ? false : tagField.tag.localeCompare(tag) > 0; | |
8d8ba23d DC |
206 | }, 1); |
207 | } | |
59e71a08 | 208 | let tagField = view.insert(index, { |
1b42bb86 DC |
209 | xtype: 'pveTag', |
210 | tag, | |
211 | mode: vm.get('editMode') ? 'editable' : 'normal', | |
212 | listeners: { | |
66dace4b | 213 | change: 'tagsChanged', |
59e71a08 DC |
214 | destroy: function() { |
215 | vm.set('tagCount', vm.get('tagCount') - 1); | |
66dace4b | 216 | me.tagsChanged(); |
1b42bb86 | 217 | }, |
e83ea30e DC |
218 | keypress: function(key) { |
219 | if (key === 'Enter') { | |
220 | me.editClick(); | |
221 | } else if (key === 'Escape') { | |
222 | me.cancelClick(); | |
223 | } | |
224 | }, | |
1b42bb86 DC |
225 | }, |
226 | }); | |
227 | ||
59e71a08 | 228 | if (isNew) { |
66dace4b | 229 | me.tagsChanged(); |
59e71a08 | 230 | tagField.selectText(); |
1b42bb86 | 231 | } |
1b42bb86 | 232 | |
59e71a08 | 233 | vm.set('tagCount', vm.get('tagCount') + 1); |
1b42bb86 DC |
234 | }, |
235 | ||
59e71a08 | 236 | addTagClick: function(event) { |
1b42bb86 | 237 | let me = this; |
59e71a08 DC |
238 | me.lookup('noTagsField').setVisible(false); |
239 | me.addTag('', true); | |
1b42bb86 DC |
240 | }, |
241 | ||
242 | cancelClick: function() { | |
243 | this.toggleEdit(true); | |
244 | }, | |
245 | ||
246 | editClick: function() { | |
247 | this.toggleEdit(false); | |
248 | }, | |
249 | ||
250 | init: function(view) { | |
251 | let me = this; | |
252 | if (view.tags) { | |
253 | me.loadTags(view.tags); | |
254 | } | |
1b48b8b7 | 255 | me.getViewModel().set('canEdit', view.canEdit); |
1b42bb86 DC |
256 | |
257 | me.mon(Ext.GlobalEvents, 'loadedUiOptions', () => { | |
731436ee | 258 | view.toggleCls('hide-handles', PVE.UIOptions.shouldSortTags()); |
8d8ba23d | 259 | me.loadTags(me.oldTags, true); // refresh tag colors and order |
1b42bb86 DC |
260 | }); |
261 | }, | |
262 | }, | |
263 | ||
264 | viewModel: { | |
265 | data: { | |
266 | tagCount: 0, | |
267 | editMode: false, | |
1b48b8b7 | 268 | canEdit: true, |
66dace4b | 269 | isDirty: false, |
1b42bb86 DC |
270 | }, |
271 | ||
272 | formulas: { | |
273 | hideNoTags: function(get) { | |
1b48b8b7 DC |
274 | return get('tagCount') !== 0 || !get('canEdit'); |
275 | }, | |
276 | hideEditBtn: function(get) { | |
277 | return get('editMode') || !get('canEdit'); | |
1b42bb86 DC |
278 | }, |
279 | }, | |
280 | }, | |
281 | ||
282 | loadTags: function() { | |
283 | return this.getController().loadTags(...arguments); | |
284 | }, | |
285 | ||
286 | items: [ | |
287 | { | |
288 | xtype: 'box', | |
59e71a08 | 289 | reference: 'noTagsField', |
1b42bb86 DC |
290 | bind: { |
291 | hidden: '{hideNoTags}', | |
292 | }, | |
293 | html: gettext('No Tags'), | |
09b65aff TL |
294 | style: { |
295 | opacity: 0.5, | |
296 | }, | |
1b42bb86 DC |
297 | }, |
298 | { | |
59e71a08 DC |
299 | xtype: 'button', |
300 | iconCls: 'fa fa-plus', | |
301 | tooltip: gettext('Add Tag'), | |
1b42bb86 DC |
302 | bind: { |
303 | hidden: '{!editMode}', | |
304 | }, | |
305 | hidden: true, | |
59e71a08 DC |
306 | margin: '0 8 0 5', |
307 | ui: 'default-toolbar', | |
308 | handler: 'addTagClick', | |
1b42bb86 DC |
309 | }, |
310 | { | |
59e71a08 DC |
311 | xtype: 'tbseparator', |
312 | ui: 'horizontal', | |
313 | bind: { | |
314 | hidden: '{!editMode}', | |
315 | }, | |
1b42bb86 | 316 | hidden: true, |
59e71a08 DC |
317 | }, |
318 | { | |
319 | xtype: 'button', | |
320 | iconCls: 'fa fa-times', | |
321 | tooltip: gettext('Cancel Edit'), | |
1b42bb86 DC |
322 | bind: { |
323 | hidden: '{!editMode}', | |
324 | }, | |
59e71a08 DC |
325 | hidden: true, |
326 | margin: '0 5 0 0', | |
327 | ui: 'default-toolbar', | |
328 | handler: 'cancelClick', | |
329 | }, | |
330 | { | |
331 | xtype: 'button', | |
332 | iconCls: 'fa fa-check', | |
333 | tooltip: gettext('Finish Edit'), | |
334 | bind: { | |
335 | hidden: '{!editMode}', | |
66dace4b | 336 | disabled: '{!isDirty}', |
1b42bb86 | 337 | }, |
59e71a08 | 338 | hidden: true, |
59e71a08 | 339 | handler: 'editClick', |
1b42bb86 DC |
340 | }, |
341 | { | |
342 | xtype: 'box', | |
343 | cls: 'pve-tag-inline-button', | |
59e71a08 | 344 | html: `<i data-qtip="${gettext('Edit Tags')}" class="fa fa-pencil"></i>`, |
1b42bb86 | 345 | bind: { |
1b48b8b7 | 346 | hidden: '{hideEditBtn}', |
1b42bb86 DC |
347 | }, |
348 | listeners: { | |
349 | click: 'editClick', | |
350 | element: 'el', | |
351 | }, | |
352 | }, | |
353 | ], | |
354 | ||
355 | listeners: { | |
356 | render: 'onRender', | |
357 | }, | |
358 | ||
359 | destroy: function() { | |
360 | let me = this; | |
361 | Ext.destroy(me.dragzone); | |
362 | Ext.destroy(me.dropzone); | |
363 | Ext.destroy(me.indicator); | |
364 | me.callParent(); | |
365 | }, | |
366 | }); |