| OLD | NEW |
| 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_">◀</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_">×</span> | 39 <label for="enabled" id="label"></label> |
| 99 <span id="key"></span> | 40 <span id="right" on-click="moveRight_">▶</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> |
| OLD | NEW |