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