]> git.proxmox.com Git - pve-manager.git/blob - www/manager6/form/TagEdit.js
ui: refactor ui option related methods into UIOptions
[pve-manager.git] / www / manager6 / form / TagEdit.js
1 Ext.define('PVE.panel.TagEditContainer', {
2 extend: 'Ext.container.Container',
3 alias: 'widget.pveTagEditContainer',
4
5 layout: {
6 type: 'hbox',
7 align: 'middle',
8 },
9
10 // set to false to hide the 'no tags' field and the edit button
11 canEdit: true,
12
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 }
38 me.tagsChanged();
39 },
40
41 onRender: function(v) {
42 let me = this;
43 let view = me.getView();
44 view.toggleCls('hide-handles', PVE.UIOptions.shouldSortTags());
45
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');
56 ddel.innerHTML = Proxmox.Utils.getTagElement(cmp.tag, PVE.UIOptions.tagOverrides);
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);
119 me.tagsChanged();
120 },
121 });
122 },
123
124 forEachTag: function(func) {
125 let me = this;
126 let view = me.getView();
127 view.items.each((field) => {
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();
138 let view = me.getView();
139 let editMode = !vm.get('editMode');
140 vm.set('editMode', editMode);
141
142 // get a current tag list for editing
143 if (editMode) {
144 PVE.UIOptions.update();
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 {
156 let toRemove = [];
157 me.forEachTag((cmp) => {
158 if (cmp.isVisible() && cmp.tag) {
159 tags.push(cmp.tag);
160 } else {
161 toRemove.push(cmp);
162 }
163 });
164 toRemove.forEach(cmp => view.remove(cmp));
165 tags = tags.join(',');
166 if (me.oldTags !== tags) {
167 me.oldTags = tags;
168 me.loadTags(tags, true);
169 me.getView().fireEvent('change', tags);
170 }
171 }
172 }
173 me.getView().updateLayout();
174 },
175
176 tagsChanged: function() {
177 let me = this;
178 let tags = [];
179 me.forEachTag(cmp => {
180 if (cmp.tag) {
181 tags.push(cmp.tag);
182 }
183 });
184 me.getViewModel().set('isDirty', me.oldTags !== tags.join(','));
185 me.forEachTag(cmp => {
186 cmp.updateFilter(tags);
187 });
188 },
189
190 addTag: function(tag, isNew) {
191 let me = this;
192 let view = me.getView();
193 let vm = me.getViewModel();
194 let index = view.items.length - 5;
195 if (PVE.UIOptions.shouldSortTags() && !isNew) {
196 index = view.items.findIndexBy(tagField => {
197 if (tagField.reference === 'noTagsField') {
198 return false;
199 }
200 if (tagField.xtype !== 'pveTag') {
201 return true;
202 }
203 let a = tagField.tag.toLowerCase();
204 let b = tag.toLowerCase();
205 return a > b ? true : a < b ? false : tagField.tag.localeCompare(tag) > 0;
206 }, 1);
207 }
208 let tagField = view.insert(index, {
209 xtype: 'pveTag',
210 tag,
211 mode: vm.get('editMode') ? 'editable' : 'normal',
212 listeners: {
213 change: 'tagsChanged',
214 destroy: function() {
215 vm.set('tagCount', vm.get('tagCount') - 1);
216 me.tagsChanged();
217 },
218 keypress: function(key) {
219 if (key === 'Enter') {
220 me.editClick();
221 } else if (key === 'Escape') {
222 me.cancelClick();
223 }
224 },
225 },
226 });
227
228 if (isNew) {
229 me.tagsChanged();
230 tagField.selectText();
231 }
232
233 vm.set('tagCount', vm.get('tagCount') + 1);
234 },
235
236 addTagClick: function(event) {
237 let me = this;
238 me.lookup('noTagsField').setVisible(false);
239 me.addTag('', true);
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 }
255 me.getViewModel().set('canEdit', view.canEdit);
256
257 me.mon(Ext.GlobalEvents, 'loadedUiOptions', () => {
258 view.toggleCls('hide-handles', PVE.UIOptions.shouldSortTags());
259 me.loadTags(me.oldTags, true); // refresh tag colors and order
260 });
261 },
262 },
263
264 viewModel: {
265 data: {
266 tagCount: 0,
267 editMode: false,
268 canEdit: true,
269 isDirty: false,
270 },
271
272 formulas: {
273 hideNoTags: function(get) {
274 return get('tagCount') !== 0 || !get('canEdit');
275 },
276 hideEditBtn: function(get) {
277 return get('editMode') || !get('canEdit');
278 },
279 },
280 },
281
282 loadTags: function() {
283 return this.getController().loadTags(...arguments);
284 },
285
286 items: [
287 {
288 xtype: 'box',
289 reference: 'noTagsField',
290 bind: {
291 hidden: '{hideNoTags}',
292 },
293 html: gettext('No Tags'),
294 style: {
295 opacity: 0.5,
296 },
297 },
298 {
299 xtype: 'button',
300 iconCls: 'fa fa-plus',
301 tooltip: gettext('Add Tag'),
302 bind: {
303 hidden: '{!editMode}',
304 },
305 hidden: true,
306 margin: '0 8 0 5',
307 ui: 'default-toolbar',
308 handler: 'addTagClick',
309 },
310 {
311 xtype: 'tbseparator',
312 ui: 'horizontal',
313 bind: {
314 hidden: '{!editMode}',
315 },
316 hidden: true,
317 },
318 {
319 xtype: 'button',
320 iconCls: 'fa fa-times',
321 tooltip: gettext('Cancel Edit'),
322 bind: {
323 hidden: '{!editMode}',
324 },
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}',
336 disabled: '{!isDirty}',
337 },
338 hidden: true,
339 handler: 'editClick',
340 },
341 {
342 xtype: 'box',
343 cls: 'pve-tag-inline-button',
344 html: `<i data-qtip="${gettext('Edit Tags')}" class="fa fa-pencil"></i>`,
345 bind: {
346 hidden: '{hideEditBtn}',
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 });