Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(267)

Side by Side Diff: tracing/tracing/ui/base/grouping_table_groupby_picker.html

Issue 2458873002: Improve usability of groupby-picker. (Closed)
Patch Set: . Created 4 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | tracing/tracing/ui/base/grouping_table_groupby_picker_test.html » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 <!DOCTYPE html> 1 <!DOCTYPE html>
2 <!-- 2 <!--
3 Copyright (c) 2016 The Chromium Authors. All rights reserved. 3 Copyright (c) 2016 The Chromium Authors. All rights reserved.
4 Use of this source code is governed by a BSD-style license that can be 4 Use of this source code is governed by a BSD-style license that can be
5 found in the LICENSE file. 5 found in the LICENSE file.
6 --> 6 -->
7 7
8 <link rel="import" href="/tracing/base/iteration_helpers.html"> 8 <link rel="import" href="/tracing/base/iteration_helpers.html">
9 <link rel="import" href="/tracing/base/settings.html"> 9 <link rel="import" href="/tracing/base/settings.html">
10 <link rel="import" href="/tracing/ui/base/dropdown.html"> 10 <link rel="import" href="/tracing/ui/base/dropdown.html">
11 11
12 <dom-module id='tr-ui-b-grouping-table-groupby-picker'> 12 <dom-module id='tr-ui-b-grouping-table-groupby-picker'>
13 <template> 13 <template>
14 <style> 14 <style>
15 :host { 15 #container {
16 display: flex;
17 align-items: center;
18 }
19
20 :host(:not([vertical])), :host(:not([vertical])) groups {
21 flex-direction: row;
22 }
23
24 :host([vertical]), :host([vertical]) groups {
25 flex-direction: column;
26 }
27
28 groups {
29 -webkit-user-select: none;
30 display: flex; 16 display: flex;
31 } 17 }
32 18 #container *:not(:first-child) {
33 possible-group { 19 padding-left: 3px;
34 display: span; 20 border-left: 1px solid black;
35 padding-right: 10px; 21 margin-left: 3px;
36 padding-left: 10px;
37 } 22 }
38 </style> 23 </style>
39 24
40 <groups id="groups"></groups> 25 <div id="container"></div>
41 <tr-ui-b-dropdown id="add_group"></tr-ui-b-dropdown>
42 </template> 26 </template>
43 </dom-module> 27 </dom-module>
44 28
45 <dom-module id="tr-ui-b-grouping-table-groupby-picker-group"> 29 <dom-module id="tr-ui-b-grouping-table-groupby-picker-group">
46 <template> 30 <template>
47 <style> 31 <style>
48 :host { 32 :host {
49 white-space: nowrap; 33 white-space: nowrap;
50 border: 3px solid white;
51 background-color: #dddddd;
52 cursor: move;
53 }
54
55 :host(:not([vertical])) {
56 display: inline;
57 }
58
59 :host([vertical]) {
60 display: block;
61 }
62
63 :host(:not([vertical]).drop-before) {
64 border-left: 3px solid black;
65 }
66
67 :host([vertical].drop-before) {
68 border-top: 3px solid black;
69 }
70
71 :host(:not([vertical]).drop-after) {
72 border-right: 3px solid black;
73 }
74
75 :host([vertical].drop-after) {
76 border-bottom: 3px solid black;
77 }
78
79 :host([dragging]) {
80 opacity: 0.5;
81 }
82
83 #remove {
84 visibility: hidden;
85 padding-left: 3px;
86 width: 20px;
87 height: 20px;
88 cursor: auto;
89 }
90
91 #key {
92 padding-right: 3px;
93 } 34 }
94 </style> 35 </style>
95 36
96 <!-- TODO(eakuefner): Use an iron-icon here once 37 <span id="left" on-click="moveLeft_">&#9664;</span>
97 https://github.com/catapult-project/catapult/issues/2772 is fixed. --> 38 <input type="checkbox" id="enabled" on-change="onEnableChanged_">
98 <span id="remove" on-click="remove_">&times;</span> 39 <label for="enabled" id="label"></label>
99 <span id="key"></span> 40 <span id="right" on-click="moveRight_">&#9654;</span>
100 </template> 41 </template>
101 </dom-module> 42 </dom-module>
102 43
103 <script> 44 <script>
104 'use strict'; 45 'use strict';
105 46
106 tr.exportTo('tr.ui.b', function() { 47 tr.exportTo('tr.ui.b', function() {
107 var THIS_DOC = document.currentScript.ownerDocument; 48 var THIS_DOC = document.currentScript.ownerDocument;
108 49
109 Polymer({ 50 Polymer({
110 is: 'tr-ui-b-grouping-table-groupby-picker-group', 51 is: 'tr-ui-b-grouping-table-groupby-picker-group',
111 52
112 created: function() { 53 created: function() {
113 this.picker_ = undefined; 54 this.picker_ = undefined;
114 this.group_ = undefined; 55 this.group_ = undefined;
115 }, 56 },
116 57
117 ready: function() {
118 this.setAttribute('draggable', true);
119 this.addEventListener('mouseover', this.onMouseOver_.bind(this));
120 this.addEventListener('mouseleave', this.onMouseLeave_.bind(this));
121 this.addEventListener('dragstart', this.onDragStart_.bind(this));
122 this.addEventListener('dragover', this.onDragOver_.bind(this));
123 },
124
125 set group(g) {
126 this.group_ = g;
127 this.$.key.textContent = g.label;
128 },
129
130 get key() {
131 return this.group_.key;
132 },
133
134 get picker() { 58 get picker() {
135 return this.picker_; 59 return this.picker_;
136 }, 60 },
137 61
138 set picker(picker) { 62 set picker(picker) {
139 this.picker_ = picker; 63 this.picker_ = picker;
140 this.vertical = picker.vertical;
141 }, 64 },
142 65
143 // TODO(benjhayden): Use data-binding? 66 get group() {
144 get vertical() { 67 return this.group_;
145 return this.getAttribute('vertical');
146 }, 68 },
147 69
148 set vertical(vertical) { 70 set group(g) {
149 if (vertical) 71 this.group_ = g;
150 this.setAttribute('vertical', true); 72 this.$.label.textContent = g.label;
151 else
152 this.removeAttribute('vertical');
153 }, 73 },
154 74
155 onMouseOver_: function(event) { 75 get enabled() {
156 this.$.remove.style.visibility = 'visible'; 76 return this.$.enabled.checked;
157 }, 77 },
158 78
159 onMouseLeave_: function(event) { 79 set enabled(enabled) {
160 this.$.remove.style.visibility = 'hidden'; 80 this.$.enabled.checked = enabled;
81 if (!this.enabled) {
82 this.$.left.style.display = 'none';
83 this.$.right.style.display = 'none';
84 }
161 }, 85 },
162 86
163 onDragStart_: function(event) { 87 set isFirst(isFirst) {
164 event.dataTransfer.effectAllowed = 'move'; 88 this.$.left.style.display = (!this.enabled || isFirst) ? 'none' :
165 this.setAttribute('dragging', true); 89 'inline';
166 }, 90 },
167 91
168 onDragOver_: function(event) { 92 set isLast(isLast) {
169 event.preventDefault(); // Allows us to drop. 93 this.$.right.style.display = (!this.enabled || isLast) ? 'none' :
170 event.dataTransfer.dropEffect = 'move'; 94 'inline';
171
172 this.picker.clearDragIndicators_();
173 if (this.picker.shouldDropBefore_(this, event)) {
174 this.classList.add('drop-before');
175 if (this.previousElementSibling)
176 this.previousElementSibling.classList.add('drop-after');
177 } else {
178 this.classList.add('drop-after');
179 if (this.nextElementSibling)
180 this.nextElementSibling.classList.add('drop-before');
181 }
182 return false;
183 }, 95 },
184 96
185 remove_: function(event) { 97 moveLeft_: function() {
186 var newKeys = this.picker.currentGroupKeys.slice(); 98 this.picker.moveLeft_(this);
187 newKeys.splice(newKeys.indexOf(this.key), 1); 99 },
188 this.picker.currentGroupKeys = newKeys; 100
101 moveRight_: function() {
102 this.picker.moveRight_(this);
103 },
104
105 onEnableChanged_: function() {
106 if (!this.enabled) {
107 this.$.left.style.display = 'none';
108 this.$.right.style.display = 'none';
109 }
110 this.picker.onEnableChanged_(this);
189 } 111 }
190 }); 112 });
191 113
192 Polymer({ 114 Polymer({
193 is: 'tr-ui-b-grouping-table-groupby-picker', 115 is: 'tr-ui-b-grouping-table-groupby-picker',
194 116
195 created: function() { 117 created: function() {
196 this.currentGroupKeys_ = undefined; 118 this.settingsKey_ = undefined;
197 this.defaultGroupKeys_ = undefined;
198 this.possibleGroups_ = [];
199 this.settingsKey_ = [];
200 },
201
202 ready: function() {
203 Polymer.dom(this.$.add_group.iconElement).textContent = 'Add another...';
204 this.addEventListener('dragend', this.onDragEnd_.bind(this));
205 this.addEventListener('drop', this.onDrop_.bind(this));
206 },
207
208 get defaultGroupKeys() {
209 return this.defaultGroupKeys_;
210 },
211
212 set vertical(vertical) {
213 if (vertical)
214 this.setAttribute('vertical', true);
215 else
216 this.removeAttribute('vertical');
217
218 for (var groupEl of this.$.groups.childNodes)
219 groupEl.vertical = vertical;
220 },
221
222 get vertical() {
223 return this.getAttribute('vertical');
224 },
225
226 set defaultGroupKeys(defaultGroupKeys) {
227 this.defaultGroupKeys_ = defaultGroupKeys;
228 this.maybeInit_();
229 },
230
231 get possibleGroups() {
232 return this.possibleGroups_;
233 },
234
235 set possibleGroups(possibleGroups) {
236 this.possibleGroups_ = possibleGroups;
237 this.maybeInit_();
238 }, 119 },
239 120
240 get settingsKey() { 121 get settingsKey() {
241 return this.settingsKey_; 122 return this.settingsKey_;
242 }, 123 },
243 124
244 set settingsKey(settingsKey) { 125 set settingsKey(settingsKey) {
245 this.settingsKey_ = settingsKey; 126 this.settingsKey_ = settingsKey;
246 this.maybeInit_(); 127 if (this.$.container.children.length) {
128 this.restoreSetting_();
129 }
247 }, 130 },
248 131
249 maybeInit_: function() { 132 restoreSetting_: function() {
250 if (!this.settingsKey_ || 133 this.currentGroupKeys = tr.b.Settings.get(this.settingsKey_,
251 !this.defaultGroupKeys_ || 134 this.currentGroupKeys);
252 !this.possibleGroups_) { 135 },
136
137 get possibleGroups() {
138 return [...this.$.container.children].map(groupEl => groupEl.group);
139 },
140
141 set possibleGroups(possibleGroups) {
142 Polymer.dom(this.$.container).textContent = '';
143 for (var i = 0; i < possibleGroups.length; ++i) {
144 var groupEl = document.createElement(
145 'tr-ui-b-grouping-table-groupby-picker-group');
146 groupEl.picker = this;
147 groupEl.group = possibleGroups[i];
148 Polymer.dom(this.$.container).appendChild(groupEl);
149 }
150 this.restoreSetting_();
151 this.updateFirstLast_();
152 },
153
154 updateFirstLast_: function() {
155 var groupEls = this.$.container.children;
156 var enabledGroupEls = [...groupEls].filter(el => el.enabled);
157 for (var i = 0; i < enabledGroupEls.length; ++i) {
158 enabledGroupEls[i].isFirst = i === 0;
159 enabledGroupEls[i].isLast = i === enabledGroupEls.length - 1;
160 }
161 },
162
163 get currentGroupKeys() {
164 return this.currentGroups.map(group => group.key);
165 },
166
167 get currentGroups() {
168 var groups = [];
169 for (var groupEl of this.$.container.children) {
170 if (groupEl.enabled) {
171 groups.push(groupEl.group);
172 }
173 }
174 return groups;
175 },
176
177 set currentGroupKeys(newKeys) {
178 if (!tr.b.compareArrays(this.currentGroupKeys, newKeys,
179 (x, y) => x.localeCompare(y))) {
253 return; 180 return;
254 } 181 }
255 182
256 this.currentGroupKeys = tr.b.Settings.get( 183 var possibleGroups = new Map();
257 this.settingsKey_, this.defaultGroupKeys_); 184 for (var group of this.possibleGroups) {
185 possibleGroups.set(group.key, group);
186 }
187
188 var groupEls = this.$.container.children;
189
190 var i = 0;
191 for (i = 0; i < newKeys.length; ++i) {
192 var group = possibleGroups.get(newKeys[i]);
193 if (group === undefined) {
194 continue;
195 }
196 groupEls[i].group = group;
197 groupEls[i].enabled = true;
198 possibleGroups.delete(newKeys[i]);
199 }
200
201 for (var group of possibleGroups.values()) {
202 groupEls[i].group = group;
203 groupEls[i].enabled = false;
204 ++i;
205 }
206
207 this.updateFirstLast_();
208 this.onCurrentGroupsChanged_();
258 }, 209 },
259 210
260 get currentGroupKeys() { 211 moveLeft_: function(groupEl) {
261 return this.currentGroupKeys_; 212 var reference = groupEl.previousSibling;
262 }, 213 Polymer.dom(this.$.container).removeChild(groupEl);
214 Polymer.dom(this.$.container).insertBefore(groupEl, reference);
215 this.updateFirstLast_();
263 216
264 get currentGroups() { 217 if (groupEl.enabled) {
265 var groupsByKey = {}; 218 this.onCurrentGroupsChanged_();
266 for (var group of this.possibleGroups_)
267 groupsByKey[group.key] = group;
268 return this.currentGroupKeys.map(groupKey => groupsByKey[groupKey]);
269 },
270
271 set currentGroupKeys(currentGroupKeys) {
272 if (this.currentGroupKeys_ === currentGroupKeys)
273 return;
274
275 if (!(currentGroupKeys instanceof Array))
276 throw new Error('Must be array');
277
278 var availableGroupKeys = new Set();
279 for (var group of this.possibleGroups_)
280 availableGroupKeys.add(group.key);
281 this.currentGroupKeys_ = currentGroupKeys.filter(
282 k => availableGroupKeys.has(k));
283 this.updateGroups_();
284
285 tr.b.Settings.set(
286 this.settingsKey_, this.currentGroupKeys_);
287
288 this.dispatchEvent(new tr.b.Event('current-groups-changed'));
289 },
290
291 /**
292 * @return {undefined|Element}
293 */
294 get draggingGroupElement() {
295 for (var group of this.$.groups.children)
296 if (group.getAttribute('dragging'))
297 return group;
298 return undefined;
299 },
300
301 shouldDropBefore_: function(groupEl, event) {
302 var dragBoxRect = this.draggingGroupElement.getBoundingClientRect();
303 var dropBoxRect = groupEl.getBoundingClientRect();
304 // compare horizontally if drag and drop overlap vertically
305 var dragVertRange = tr.b.Range.fromExplicitRange(
306 dragBoxRect.top, dragBoxRect.bottom);
307 var dropVertRange = tr.b.Range.fromExplicitRange(
308 dropBoxRect.top, dropBoxRect.bottom);
309 if (dragVertRange.intersectsRangeInclusive(dropVertRange))
310 return event.clientX < ((dropBoxRect.left + dropBoxRect.right) / 2);
311 return event.clientY < ((dropBoxRect.top + dropBoxRect.bottom) / 2);
312 },
313
314 clearDragIndicators_: function() {
315 for (var groupEl of this.$.groups.children) {
316 groupEl.classList.remove('drop-before');
317 groupEl.classList.remove('drop-after');
318 } 219 }
319 }, 220 },
320 221
321 onDragEnd_: function(event) { 222 moveRight_: function(groupEl) {
322 this.clearDragIndicators_(); 223 var reference = groupEl.nextSibling.nextSibling;
323 for (var groupEl of this.$.groups.children) 224 Polymer.dom(this.$.container).removeChild(groupEl);
324 groupEl.removeAttribute('dragging'); 225 if (reference) {
226 Polymer.dom(this.$.container).insertBefore(groupEl, reference);
227 } else {
228 Polymer.dom(this.$.container).appendChild(groupEl);
229 }
230 this.updateFirstLast_();
231
232 if (groupEl.enabled) {
233 this.onCurrentGroupsChanged_();
234 }
325 }, 235 },
326 236
327 onDrop_: function(event) { 237 onCurrentGroupsChanged_: function() {
328 event.stopPropagation(); // stops the browser from redirecting. 238 this.dispatchEvent(new tr.b.Event('current-groups-changed'));
329 239 tr.b.Settings.set(this.settingsKey_, this.currentGroupKeys);
330 var draggingGroupEl = this.draggingGroupElement;
331 var dropBeforeEl = undefined;
332 var dropAfterEl = undefined;
333 for (var groupEl of this.$.groups.children) {
334 if (groupEl.classList.contains('drop-before')) {
335 dropBeforeEl = groupEl;
336 break;
337 }
338 if (groupEl.classList.contains('drop-after')) {
339 dropAfterEl = groupEl;
340 break;
341 }
342 }
343
344 if (!dropBeforeEl && !dropAfterEl)
345 return;
346
347 this.$.groups.removeChild(draggingGroupEl);
348 var lastGroupEl = this.$.groups.children[
349 this.$.groups.children.length - 1];
350
351 if (dropBeforeEl)
352 this.$.groups.insertBefore(draggingGroupEl, dropBeforeEl);
353 else if (dropAfterEl === lastGroupEl)
354 this.$.groups.appendChild(draggingGroupEl);
355 else
356 this.$.groups.insertBefore(draggingGroupEl, dropAfterEl.nextSibling);
357
358 var currentGroupKeys = [];
359 for (var group of this.$.groups.children)
360 currentGroupKeys.push(group.key);
361 this.currentGroupKeys = currentGroupKeys;
362 return false;
363 }, 240 },
364 241
365 updateGroups_: function() { 242 onEnableChanged_: function(groupEl) {
366 Polymer.dom(this.$.groups).textContent = ''; 243 this.updateFirstLast_();
367 Polymer.dom(this.$.add_group).textContent = ''; 244 this.onCurrentGroupsChanged_();
368
369 var unusedGroups = {};
370 var groupsByKey = {};
371 for (var group of this.possibleGroups_) {
372 unusedGroups[group.key] = group;
373 groupsByKey[group.key] = group;
374 }
375
376 for (var key of this.currentGroupKeys_)
377 delete unusedGroups[key];
378
379 // Create groups.
380 for (var key of this.currentGroupKeys_) {
381 var group = groupsByKey[key];
382 var groupEl = document.createElement(
383 'tr-ui-b-grouping-table-groupby-picker-group');
384 groupEl.picker = this;
385 groupEl.group = group;
386 Polymer.dom(this.$.groups).appendChild(groupEl);
387 }
388
389 // Adjust dropdown.
390 tr.b.iterItems(unusedGroups, function(key, group) {
391 var groupEl = document.createElement('possible-group');
392 Polymer.dom(groupEl).textContent = group.label;
393 groupEl.addEventListener('click', function() {
394 var newKeys = this.currentGroupKeys.slice();
395 newKeys.push(key);
396 this.currentGroupKeys = newKeys;
397 this.$.add_group.close();
398 }.bind(this));
399 Polymer.dom(this.$.add_group).appendChild(groupEl);
400 }, this);
401
402 // Hide dropdown if needed.
403 if (tr.b.dictionaryLength(unusedGroups) === 0) {
404 this.$.add_group.style.display = 'none';
405 } else {
406 this.$.add_group.style.display = '';
407 }
408 } 245 }
409 }); 246 });
410 247
411 return { 248 return {
412 }; 249 };
413 }); 250 });
414 </script> 251 </script>
OLDNEW
« no previous file with comments | « no previous file | tracing/tracing/ui/base/grouping_table_groupby_picker_test.html » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698