]> git.proxmox.com Git - pve-manager.git/blob - www/manager6/form/TagEdit.js
18d8927d7c1dc09208a91c20278e5e3bf832bb6e
[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 me.updateFilter();
34 view.suspendLayout = false;
35 view.updateLayout();
36 if (!force) {
37 me.oldTags = tagstring;
38 }
39 },
40
41 onRender: function(v) {
42 let me = this;
43 let view = me.getView();
44 view.toggleCls('hide-handles', PVE.Utils.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.Utils.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 },
120 });
121 },
122
123 forEachTag: function(func) {
124 let me = this;
125 let view = me.getView();
126 view.items.each((field) => {
127 if (field.getXType() === 'pveTag') {
128 func(field);
129 }
130 return true;
131 });
132 },
133
134 toggleEdit: function(cancel) {
135 let me = this;
136 let vm = me.getViewModel();
137 let view = me.getView();
138 let editMode = !vm.get('editMode');
139 vm.set('editMode', editMode);
140
141 // get a current tag list for editing
142 if (editMode) {
143 PVE.Utils.updateUIOptions();
144 }
145
146 me.forEachTag((tag) => {
147 tag.setMode(editMode ? 'editable' : 'normal');
148 });
149
150 if (!vm.get('editMode')) {
151 let tags = [];
152 if (cancel) {
153 me.loadTags(me.oldTags, true);
154 } else {
155 let toRemove = [];
156 me.forEachTag((cmp) => {
157 if (cmp.isVisible() && cmp.tag) {
158 tags.push(cmp.tag);
159 } else {
160 toRemove.push(cmp);
161 }
162 });
163 toRemove.forEach(cmp => view.remove(cmp));
164 tags = tags.join(',');
165 if (me.oldTags !== tags) {
166 me.oldTags = tags;
167 me.loadTags(tags, true);
168 me.getView().fireEvent('change', tags);
169 }
170 }
171 }
172 me.getView().updateLayout();
173 },
174
175 updateFilter: function() {
176 let me = this;
177 let tags = [];
178 me.forEachTag(cmp => {
179 if (cmp.tag) {
180 tags.push(cmp.tag);
181 }
182 });
183 me.forEachTag(cmp => {
184 cmp.updateFilter(tags);
185 });
186 },
187
188 addTag: function(tag, isNew) {
189 let me = this;
190 let view = me.getView();
191 let vm = me.getViewModel();
192 let index = view.items.length - 5;
193 if (PVE.Utils.shouldSortTags() && !isNew) {
194 index = view.items.findIndexBy(tagField => {
195 if (tagField.reference === 'noTagsField') {
196 return false;
197 }
198 if (tagField.xtype !== 'pveTag') {
199 return true;
200 }
201 let a = tagField.tag.toLowerCase();
202 let b = tag.toLowerCase();
203 return a > b ? true : a < b ? false : tagField.tag.localeCompare(tag) > 0;
204 }, 1);
205 }
206 let tagField = view.insert(index, {
207 xtype: 'pveTag',
208 tag,
209 mode: vm.get('editMode') ? 'editable' : 'normal',
210 listeners: {
211 change: (field, newTag) => {
212 me.updateFilter();
213 },
214 destroy: function() {
215 vm.set('tagCount', vm.get('tagCount') - 1);
216 },
217 keypress: function(key) {
218 if (key === 'Enter') {
219 me.editClick();
220 } else if (key === 'Escape') {
221 me.cancelClick();
222 }
223 },
224 },
225 });
226
227 if (isNew) {
228 me.updateFilter();
229 tagField.selectText();
230 }
231
232 vm.set('tagCount', vm.get('tagCount') + 1);
233 },
234
235 addTagClick: function(event) {
236 let me = this;
237 me.lookup('noTagsField').setVisible(false);
238 me.addTag('', true);
239 },
240
241 cancelClick: function() {
242 this.toggleEdit(true);
243 },
244
245 editClick: function() {
246 this.toggleEdit(false);
247 },
248
249 init: function(view) {
250 let me = this;
251 if (view.tags) {
252 me.loadTags(view.tags);
253 }
254 me.getViewModel().set('canEdit', view.canEdit);
255
256 me.mon(Ext.GlobalEvents, 'loadedUiOptions', () => {
257 view.toggleCls('hide-handles', PVE.Utils.shouldSortTags());
258 me.loadTags(me.oldTags, true); // refresh tag colors and order
259 });
260 },
261 },
262
263 viewModel: {
264 data: {
265 tagCount: 0,
266 editMode: false,
267 canEdit: true,
268 },
269
270 formulas: {
271 hideNoTags: function(get) {
272 return get('tagCount') !== 0 || !get('canEdit');
273 },
274 hideEditBtn: function(get) {
275 return get('editMode') || !get('canEdit');
276 },
277 },
278 },
279
280 loadTags: function() {
281 return this.getController().loadTags(...arguments);
282 },
283
284 items: [
285 {
286 xtype: 'box',
287 reference: 'noTagsField',
288 bind: {
289 hidden: '{hideNoTags}',
290 },
291 html: gettext('No Tags'),
292 style: {
293 opacity: 0.5,
294 },
295 },
296 {
297 xtype: 'button',
298 iconCls: 'fa fa-plus',
299 tooltip: gettext('Add Tag'),
300 bind: {
301 hidden: '{!editMode}',
302 },
303 hidden: true,
304 margin: '0 8 0 5',
305 ui: 'default-toolbar',
306 handler: 'addTagClick',
307 },
308 {
309 xtype: 'tbseparator',
310 ui: 'horizontal',
311 bind: {
312 hidden: '{!editMode}',
313 },
314 hidden: true,
315 },
316 {
317 xtype: 'button',
318 iconCls: 'fa fa-times',
319 tooltip: gettext('Cancel Edit'),
320 bind: {
321 hidden: '{!editMode}',
322 },
323 hidden: true,
324 margin: '0 5 0 0',
325 ui: 'default-toolbar',
326 handler: 'cancelClick',
327 },
328 {
329 xtype: 'button',
330 iconCls: 'fa fa-check',
331 tooltip: gettext('Finish Edit'),
332 bind: {
333 hidden: '{!editMode}',
334 },
335 hidden: true,
336 ui: 'default-toolbar',
337 handler: 'editClick',
338 },
339 {
340 xtype: 'box',
341 cls: 'pve-tag-inline-button',
342 html: `<i data-qtip="${gettext('Edit Tags')}" class="fa fa-pencil"></i>`,
343 bind: {
344 hidden: '{hideEditBtn}',
345 },
346 listeners: {
347 click: 'editClick',
348 element: 'el',
349 },
350 },
351 ],
352
353 listeners: {
354 render: 'onRender',
355 },
356
357 destroy: function() {
358 let me = this;
359 Ext.destroy(me.dragzone);
360 Ext.destroy(me.dropzone);
361 Ext.destroy(me.indicator);
362 me.callParent();
363 },
364 });