1<!DOCTYPE html>
2<!--
3Copyright (c) 2016 The Chromium Authors. All rights reserved.
4Use of this source code is governed by a BSD-style license that can be
5found in the LICENSE file.
6-->
7<link rel="import" href="/tracing/base/iteration_helpers.html">
8<link rel="import" href="/tracing/base/settings.html">
9<link rel="import" href="/tracing/ui/base/dropdown.html">
10
11<polymer-element name="tr-ui-b-grouping-table-groupby-picker">
12  <template>
13    <style>
14    :host {
15      display: flex;
16      flex-direction: row;
17      align-items: center;
18    }
19    groups {
20      -webkit-user-select: none;
21      display: flex;
22      flex-direction: row;
23      padding-left: 10px;
24    }
25
26    group, possible-group {
27      display: span;
28      padding-right: 10px;
29      padding-left: 10px;
30    }
31
32    group {
33      border-left: 1px solid rgba(0,0,0,0);
34      cursor: move;
35    }
36
37    group.dragging {
38      opacity: 0.2;
39    }
40
41    group.drop-targeted {
42      border-left: 1px solid black;
43    }
44
45
46    #remove {
47      cursor: default;
48    }
49
50    #remove:not([hovered]) {
51      visibility: hidden;
52    }
53    </style>
54    <groups>
55    </groups>
56    <tr-ui-b-dropdown id="add-group"></tr-ui-b-dropdown>
57  </template>
58</polymer-element>
59
60<template id="tr-ui-b-grouping-table-groupby-picker-group-template">
61  <span id="key"></span>
62  <span id="remove">&times;</span>
63</template>
64
65<script>
66'use strict';
67
68tr.exportTo('tr.ui.b', function() {
69  var THIS_DOC = document._currentScript.ownerDocument;
70
71  Polymer('tr-ui-b-grouping-table-groupby-picker', {
72    created: function() {
73      this.needsInit_ = true;
74      this.defaultGroupKeys_ = undefined;
75      this.possibleGroups_ = [];
76      this.settingsKey_ = [];
77
78      this.currentGroupKeys_ = undefined;
79
80      this.dragging_ = false;
81    },
82
83    get defaultGroupKeys() {
84      return this.defaultGroupKeys_;
85    },
86
87    set defaultGroupKeys(defaultGroupKeys) {
88      if (!this.needsInit_)
89        throw new Error('Already initialized.');
90      this.defaultGroupKeys_ = defaultGroupKeys;
91      this.maybeInit_();
92    },
93
94    get possibleGroups() {
95      return this.possibleGroups_;
96    },
97
98    set possibleGroups(possibleGroups) {
99      if (!this.needsInit_)
100        throw new Error('Already initialized.');
101      this.possibleGroups_ = possibleGroups;
102      this.maybeInit_();
103    },
104
105    get settingsKey() {
106      return this.settingsKey_;
107    },
108
109    set settingsKey(settingsKey) {
110      if (!this.needsInit_)
111        throw new Error('Already initialized.');
112      this.settingsKey_ = settingsKey;
113      this.maybeInit_();
114    },
115
116    maybeInit_: function() {
117      if (!this.needsInit_)
118        return;
119
120      if (this.settingsKey_ === undefined)
121        return;
122      if (this.defaultGroupKeys_ === undefined)
123        return;
124      if (this.possibleGroups_ === undefined)
125        return;
126
127      this.needsInit_ = false;
128
129      var addGroupEl = this.shadowRoot.querySelector('#add-group');
130      addGroupEl.iconElement.textContent = 'Add another...';
131
132      this.currentGroupKeys = tr.b.Settings.get(
133        this.settingsKey_, this.defaultGroupKeys_);
134    },
135
136    get currentGroupKeys() {
137      return this.currentGroupKeys_;
138    },
139
140    get currentGroups() {
141      var groupsByKey = {};
142      this.possibleGroups_.forEach(function(group) {
143        groupsByKey[group.key] = group;
144      });
145      return this.currentGroupKeys_.map(function(groupKey) {
146        return groupsByKey[groupKey];
147      });
148    },
149
150    set currentGroupKeys(currentGroupKeys) {
151      if (this.currentGroupKeys_ === currentGroupKeys)
152        return;
153
154      if (!(currentGroupKeys instanceof Array))
155        throw new Error('Must be array');
156
157      this.currentGroupKeys_ = currentGroupKeys;
158      this.updateGroups_();
159
160      tr.b.Settings.set(
161        this.settingsKey_, this.currentGroupKeys_);
162
163      var e = new tr.b.Event('current-groups-changed');
164      this.dispatchEvent(e);
165    },
166
167    updateGroups_: function() {
168      var groupsEl = this.shadowRoot.querySelector('groups');
169      var addGroupEl = this.shadowRoot.querySelector('#add-group');
170
171      groupsEl.textContent = '';
172      addGroupEl.textContent = '';
173
174      var unusedGroups = {};
175      var groupsByKey = {};
176      this.possibleGroups_.forEach(function(group) {
177        unusedGroups[group.key] = group;
178        groupsByKey[group.key] = group;
179      });
180
181      this.currentGroupKeys_.forEach(function(key) {
182        delete unusedGroups[key];
183      });
184
185      // Create groups.
186      var groupTemplateEl = THIS_DOC.querySelector(
187          '#tr-ui-b-grouping-table-groupby-picker-group-template');
188      this.currentGroupKeys_.forEach(function(key, index) {
189        var group = groupsByKey[key];
190        var groupEl = document.createElement('group');
191        groupEl.groupKey = key;
192        groupEl.appendChild(document.importNode(groupTemplateEl.content, true));
193        groupEl.querySelector('#key').textContent = group.label;
194        groupsEl.appendChild(groupEl);
195
196        this.configureRemoveButtonForGroup_(groupEl);
197        this.configureDragAndDropForGroup_(groupEl);
198      }, this);
199
200      // Adjust dropdown.
201      tr.b.iterItems(unusedGroups, function(key, group) {
202        var groupEl = document.createElement('possible-group');
203        groupEl.textContent = group.label;
204        groupEl.addEventListener('click', function() {
205          var newKeys = this.currentGroupKeys.slice();
206          newKeys.push(key);
207          this.currentGroupKeys = newKeys;
208          addGroupEl.close();
209        }.bind(this));
210        addGroupEl.appendChild(groupEl);
211      }, this);
212
213      // Hide dropdown if needed.
214      if (tr.b.dictionaryLength(unusedGroups) == 0) {
215        addGroupEl.style.display = 'none';
216      } else {
217        addGroupEl.style.display = '';
218      }
219    },
220
221    configureRemoveButtonForGroup_: function(groupEl) {
222      var removeEl = groupEl.querySelector('#remove');
223      removeEl.addEventListener('click', function() {
224        var newKeys = this.currentGroupKeys.slice();
225        var i = newKeys.indexOf(groupEl.groupKey);
226        newKeys.splice(i, 1);
227        this.currentGroupKeys = newKeys;
228      }.bind(this));
229
230      groupEl.addEventListener('mouseenter', function() {
231        removeEl.setAttribute('hovered', true);
232      });
233      groupEl.addEventListener('mouseleave', function() {
234        removeEl.removeAttribute('hovered');
235      });
236    },
237
238    configureDragAndDropForGroup_: function(groupEl) {
239      var groupsEl = groupEl.parentElement;
240
241      groupEl.setAttribute('draggable', true);
242
243      groupEl.addEventListener('dragstart', function(e) {
244        e.dataTransfer.setData('groupKey', groupEl.groupKey);
245        groupEl.querySelector('#remove').removeAttribute('hovered');
246        groupEl.classList.add('dragging');
247        this.dragging_ = true;
248      }.bind(this));
249
250      groupEl.addEventListener('dragend', function(e) {
251        console.log(e.type, groupEl.groupKey);
252        for (var i = 0; i < groupsEl.children.length; i++)
253          groupsEl.children[i].classList.remove('drop-targeted');
254        groupEl.classList.remove('dragging');
255        this.dragging_ = false;
256      }.bind(this));
257
258      // Drop targeting.
259      groupEl.addEventListener('dragenter', function(e) {
260        if (!this.dragging_)
261          return;
262        groupEl.classList.add('drop-targeted');
263        if (this.dragging_)
264          e.preventDefault();
265      }.bind(this));
266
267      groupEl.addEventListener('dragleave', function(e) {
268        if (!this.dragging_)
269          return;
270        groupEl.classList.remove('drop-targeted');
271        e.preventDefault();
272      }.bind(this));
273
274
275      // Drop logic.
276      groupEl.addEventListener('dragover', function(e) {
277        if (!this.dragging_)
278          return;
279        e.preventDefault();
280        groupEl.classList.add('drop-targeted');
281      }.bind(this));
282
283      groupEl.addEventListener('drop', function(e) {
284        if (!this.dragging_)
285          return;
286
287        var srcKey = e.dataTransfer.getData('groupKey');
288        var dstKey = groupEl.groupKey;
289
290        if (srcKey === dstKey)
291          return;
292
293        var newKeys = this.currentGroupKeys_.slice();
294
295        var srcIndex = this.currentGroupKeys_.indexOf(srcKey);
296        newKeys.splice(srcIndex, 1);
297
298        var dstIndex = this.currentGroupKeys_.indexOf(dstKey);
299        newKeys.splice(dstIndex, 0, srcKey);
300
301        this.currentGroupKeys = newKeys;
302
303        e.dataTransfer.clearData();
304        e.preventDefault();
305        e.stopPropagation();
306      }.bind(this));
307    }
308  });
309
310  return {
311  };
312});
313</script>
314