]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
Bug 1542554 - Add bug type icons to dependency trees
authorKohei Yoshino <kohei.yoshino@gmail.com>
Tue, 21 May 2019 00:37:38 +0000 (20:37 -0400)
committerGitHub <noreply@github.com>
Tue, 21 May 2019 00:37:38 +0000 (20:37 -0400)
js/dependency-tree.js [new file with mode: 0644]
js/expanding-tree.js [deleted file]
skins/standard/dependency-tree.css
template/en/default/bug/dependency-tree.html.tmpl

diff --git a/js/dependency-tree.js b/js/dependency-tree.js
new file mode 100644 (file)
index 0000000..00b3c8a
--- /dev/null
@@ -0,0 +1,102 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This Source Code Form is "Incompatible With Secondary Licenses", as
+ * defined by the Mozilla Public License, v. 2.0. */
+
+/**
+ * Reference or define the Bugzilla app namespace.
+ * @namespace
+ */
+var Bugzilla = Bugzilla || {}; // eslint-disable-line no-var
+
+/**
+ * Activate a Dependency Tree so the user can expand/collapse trees and highlight duplicates.
+ */
+Bugzilla.DependencyTree = class DependencyTree {
+  /**
+   * Initialize a new DependencyTree instance.
+   * @param {HTMLUListElement} $tree The topmost element of a tree.
+   */
+  constructor($tree) {
+    $tree.querySelectorAll('.expander').forEach($button => {
+      $button.addEventListener('click', event => this.toggle_treeitem(event));
+    });
+
+    $tree.querySelectorAll('.duplicate-highlighter').forEach($button => {
+      $button.addEventListener('click', event => this.highlight_duplicates(event));
+    });
+
+    $tree.querySelectorAll('.summary.duplicated .bug-link').forEach($link => {
+      $link.addEventListener('mouseenter', event => this.highlight_duplicates(event));
+      $link.addEventListener('mouseleave', event => this.highlight_duplicates(event));
+    });
+  }
+
+  /**
+   * Expand or collapse one or more tree items.
+   * @param {MouseEvent} event `click` event.
+   */
+  toggle_treeitem(event) {
+    const { target, altKey, ctrlKey, metaKey, shiftKey } = event;
+    const $item = target.closest('[role="treeitem"]');
+    const expanded = $item.matches('[aria-expanded="false"]');
+    const accelKey = navigator.platform === 'MacIntel' ? metaKey && !ctrlKey : ctrlKey;
+
+    $item.setAttribute('aria-expanded', expanded);
+
+    // Do the same for the subtrees if the Ctrl/Command key is pressed
+    if (accelKey && !altKey && !shiftKey) {
+      $item.querySelectorAll('[role="treeitem"]').forEach($child => {
+        $child.setAttribute('aria-expanded', expanded);
+      });
+    }
+  }
+
+  /**
+   * Highlight one or more duplicated tree items.
+   * @param {MouseEvent} event `click`, `mouseenter` or `mouseleave` event.
+   */
+  highlight_duplicates(event) {
+    const { target, type } = event;
+    const id = Number(target.closest('[role="treeitem"]').dataset.id);
+    const pressed = type === 'click' ? target.matches('[aria-pressed="false"]') : undefined;
+
+    if (type.startsWith('mouse') && this.highlighted) {
+      return;
+    }
+
+    if (type === 'click') {
+      if (this.highlighted) {
+        // Remove existing highlights
+        document.querySelectorAll(`[role="treeitem"][data-id="${this.highlighted}"]`).forEach($item => {
+          const $highlighter = $item.querySelector('.duplicate-highlighter');
+
+          if ($highlighter) {
+            $highlighter.setAttribute('aria-pressed', 'false');
+          }
+
+          $item.querySelector('.summary').classList.remove('highlight');
+        });
+      }
+
+      target.setAttribute('aria-pressed', pressed);
+      this.highlighted = pressed ? id : undefined;
+    }
+
+    document.querySelectorAll(`[role="treeitem"][data-id="${id}"]`).forEach(($item, index) => {
+      $item.querySelector('.summary').classList.toggle('highlight', pressed);
+
+      if (index === 0 && pressed) {
+        $item.scrollIntoView();
+      }
+    });
+  }
+};
+
+window.addEventListener('DOMContentLoaded', () => {
+  document.querySelectorAll('[role="tree"]').forEach($tree => {
+    new Bugzilla.DependencyTree($tree);
+  });
+}, { once: true });
diff --git a/js/expanding-tree.js b/js/expanding-tree.js
deleted file mode 100644 (file)
index e86bff8..0000000
+++ /dev/null
@@ -1,157 +0,0 @@
-/* The contents of this file are subject to the Mozilla Public
- * License Version 1.1 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of
- * the License at http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS
- * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
- * implied. See the License for the specific language governing
- * rights and limitations under the License.
- *
- * The Original Code is the Bugzilla Bug Tracking System.
- *
- * The Initial Developer of the Original Code is Netscape Communications
- * Corporation. Portions created by Netscape are
- * Copyright (C) 1998 Netscape Communications Corporation. All
- * Rights Reserved.
- *
- * Contributor(s): Mike Shaver <shaver@mozilla.org>
- *                 Christian Reis <kiko@async.com.br>
- *                 AndrĂ© Batosti <batosti@async.com.br>
- */
-
-if (!Node) {
-    // MSIE doesn't define Node, so provide a compatibility object
-    var Node = { TEXT_NODE: 3 }
-}
-
-if (!highlighted) {
-    var highlighted = 0;
-    var highlightedclass = "";
-    var highlightedover = 0;
-}
-
-function doToggle(node, event) {
-    var deep = event.altKey || event.ctrlKey;
-
-    if (node.nodeType == Node.TEXT_NODE)
-        node = node.parentNode;
-
-    var toggle = node.nextSibling;
-    while (toggle && toggle.tagName != "UL")
-        toggle = toggle.nextSibling;
-
-    if (toggle) {
-        if (deep) {
-            var direction = toggleDisplay(toggle, node);
-            changeChildren(toggle, direction);
-        } else {
-            toggleDisplay(toggle, node);
-        }
-    }
-    /* avoid problems with default actions on links (mozilla's
-     * ctrl/shift-click defaults, for instance */
-    event.preventBubble();
-    event.preventDefault();
-    return false;
-}
-
-function changeChildren(node, direction) {
-    var item = node.firstChild;
-    while (item) {
-        /* find the LI inside the UL I got */
-        while (item && item.tagName != "LI")
-            item = item.nextSibling;
-        if (!item)
-            return;
-
-        /* got it, now find the first A */
-        var child = item.firstChild;
-        while (child && child.tagName != "A")
-            child = child.nextSibling;
-        if (!child) {
-            return
-        }
-        var bullet = child;
-
-        /* and check if it has its own sublist */
-        var sublist = item.firstChild;
-        while (sublist && sublist.tagName != "UL")
-            sublist = sublist.nextSibling;
-        if (sublist) {
-            if (direction && isClosed(sublist)) {
-                openNode(sublist, bullet);
-            } else if (!direction && !isClosed(sublist)) {
-                closeNode(sublist, bullet);
-            }
-            changeChildren(sublist, direction)
-        }
-        item = item.nextSibling;
-    }
-}
-
-function openNode(node, bullet) {
-    node.style.display = "block";
-    bullet.className = "b b_open";
-}
-
-function closeNode(node, bullet) {
-    node.style.display = "none";
-    bullet.className = "b b_closed";
-}
-
-function isClosed(node) {
-    /* XXX we should in fact check our *computed* style, not the display
-     * attribute of the current node, which may be inherited and not
-     * set. However, this really only matters when changing the default
-     * appearance of the tree through a parent style. */
-    return node.style.display == "none";
-}
-
-function toggleDisplay(node, bullet) {
-    if (isClosed(node)) {
-        openNode(node, bullet);
-        return true;
-    }
-
-    closeNode(node, bullet);
-    return false;
-}
-
-function duplicated(element) {
-    var allsumm= document.getElementsByTagName("span");
-    if (highlighted) {
-        for (i = 0;i < allsumm.length; i++) {
-            if (allsumm.item(i).id == highlighted) {
-                allsumm.item(i).className = highlightedclass;
-            }
-        }
-        if (highlighted == element) {
-            highlighted = 0;
-            return;
-        }
-    }
-    highlighted = element;
-    var elem = document.getElementById(element);
-    highlightedclass = elem.className;
-    for (var i = 0;i < allsumm.length; i++) {
-        if (allsumm.item(i).id == element) {
-            allsumm.item(i).className = "summ_h";
-        }
-    }
-}
-
-function duplicatedover(element) {
-    if (!highlighted) {
-        highlightedover = 1;
-        duplicated(element);
-    }
-}
-
-function duplicatedout(element) {
-    if (highlighted == element && highlightedover) {
-        highlightedover = 0;
-        duplicated(element);
-    }
-}
-
index c22bf4313d52802aeb7c46c767599f53e7cec90b..ed3ff0f8dbbf69447927b7ad3fbe1b5bf6ddc887 100644 (file)
@@ -5,62 +5,95 @@
  * This Source Code Form is "Incompatible With Secondary Licenses", as
  * defined by the Mozilla Public License, v. 2.0. */
 
-ul.tree {
+[role="button"] {
+  outline: 0;
+  border: 0;
+  padding: 0;
+  color: inherit;
+  background: none transparent !important;
+  box-shadow: none !important;
+  font-size: inherit;
+  font-weight: normal;
+  transition: none;
+}
+
+[role="button"] * {
+  pointer-events: none;
+}
+
+[role="tree"] {
   display: block;
-  margin-left: 1em;
-  padding-left: 0em;
+  margin: 16px 0;
+  padding: 0;
 }
 
-ul.tree ul {
+[role="tree"] ul {
   display: block;
-  padding-top: 3px;
+  margin: 0;
+  padding: 0 0 0 22px;
 }
 
-ul.tree li {
-  /* see http://www.kryogenix.org/code/browser/aqlists/ for idea */
-  padding-top: 3px;
-  text-indent: -1.2em;
-  padding-left: 0.5em;
-  padding-bottom: 3px;
+[role="tree"] li {
+  display: block;
+  margin: 8px 0;
+  padding: 0;
   list-style-type: none;
-  background: url('dependency-tree/bug-item.png') no-repeat;
 }
 
-ul.tree li a.b {
-  padding-left: 30px;
-  margin-right: -14px;
-  text-decoration: none;
+[role="tree"] .icon::before {
+  font-family: 'Material Icons';
+  font-size: 18px;
+  line-height: 100%;
+  vertical-align: top;
 }
 
-ul.tree li a.b_open {
-  background: url('dependency-tree/tree-open.png') center no-repeat;
-  cursor: pointer;
+[role="treeitem"][aria-expanded="true"] > .expander .icon {
+  transform: rotate(90deg);
 }
 
-ul.tree li a.b_closed {
-  background: url('dependency-tree/tree-closed.png') center no-repeat;
-  cursor: pointer;
+[role="treeitem"][aria-expanded="false"] > .expander .icon {
+  transform: rotate(0);
 }
 
-ul.tree a.tree_link img {
-  border: 0;
+[role="treeitem"][aria-expanded="false"] [role="group"] {
+  display: none;
 }
 
-.summ_info {
-  /* change to inline if you would like to see the full bug details
-   * displayed in the list */
-  display: none;
-  font-size: var(--font-size-small);
+.expander {
+  width: 18px;
+  height: 18px;
 }
 
-.hint {
-  margin: 0.2em;
-  padding: 0.1em;
-  font-size: var(--font-size-small);
+.expander .icon {
+  display: inline-block;
+  width: 18px;
+  height: 18px;
+  transform-origin: center;
+  transition: transform .2s;
 }
 
-.hint h3,
-.hint ul {
-  margin-top: 0.1em;
-  margin-bottom: 0.1em;
+.expander .icon::before {
+  content: '\E037';
+}
+
+.tree-link .icon::before {
+  content: '\E0B8';
+}
+
+.duplicate-highlighter[aria-pressed="true"] {
+  color: rgb(var(--accent-color-red-1));
+}
+
+.duplicate-highlighter .icon::before {
+  content: '\E417';
+}
+
+.summary.highlight .bug-link {
+  background-color: var(--selected-control-background-color) !important;
+  font-weight: 500 !important;
+}
+
+.summ_info {
+  display: none; /* change to inline if you would like to see the full bug details displayed in the list */
+  font-size: 75%;
 }
index 06955e1bb4883453dc69d145d0b1ae3fe4f08f1e..e91f64338e787b4e9c60481b1027d35ac29d7a32 100644 (file)
@@ -28,7 +28,7 @@
    title           = "Dependency tree for $terms.Bug $bugid"
    header          = "Dependency tree for
                       <a href=\"${basepath}show_bug.cgi?id=$bugid\">$terms.Bug $bugid</a>"
-   javascript_urls = ["js/expanding-tree.js"]
+   javascript_urls = ["js/dependency-tree.js"]
    style_urls      = ["skins/standard/dependency-tree.css"]
    subheader      = filtered_desc
    doc_section = "hintsandtips.html#dependencytree"
       [% END %]
     [% END %]
 
-    <ul class="tree">
+    <ul role="tree">
       [% INCLUDE display_tree tree=$tree_name %]
     </ul>
   [% END %]
     #   - tree: a hash of bug objects and of bug dependencies
     #%]
   [% bug = tree.$bugid %]
-  <li>
-    [%- INCLUDE bullet bugid=bugid tree=tree -%]
-    <span class="summ[% "_deep" IF tree.dependencies.$bugid.size %]"
-          id="[% bugid FILTER html %]"
-          [% IF global.seen.$bugid %]
-            onMouseover="duplicatedover('[% bugid FILTER html %]')"
-            onMouseout="duplicatedout('[% bugid FILTER html %]')"
-          [% END %]>
+  <li role="treeitem" data-id="[% bugid FILTER html %]"
+      [% IF tree.dependencies.$bugid.size && !global.seen.$bugid %] aria-expanded="true"[% END %]>
+    [% IF tree.dependencies.$bugid.size && ! global.seen.$bugid %]
+      <button type="button" role="button" class="iconic expander"
+              title="Click to expand or collapse this portion of the tree. Hold down the Ctrl or Command key while clicking to expand or collapse all subtrees.">
+        <span class="icon" aria-hidden="true"></span>
+      </button>
+    [% END %]
+    <span class="bug-type-label iconic" title="[% bug.bug_type FILTER html %]"
+          aria-label="[% bug.bug_type FILTER html %]" data-type="[% bug.bug_type FILTER html %]">
+      <span class="icon" aria-hidden="true"></span>
+    </span>
+    <span class="summary[% ' deep' IF tree.dependencies.$bugid.size %][% ' duplicated' IF global.seen.$bugid %]">
       [%- INCLUDE buglink bug=bug bugid=bugid %]
     </span>
     [% IF global.seen.$bugid %]
-      <b><a title="Already displayed above; click to locate"
-            onclick="duplicated('[% bugid FILTER html %]')"
-            href="#b[% bugid %]">(*)</a></b>
+      <button type="button" role="button" class="iconic duplicate-highlighter"
+              title="Already displayed above; click to locate" aria-pressed="false">
+        <span class="icon" aria-hidden="true"></span>
+      </button>
     [% ELSIF tree.dependencies.$bugid.size %]
-      <ul>
+      <ul role="group">
         [% FOREACH depid = tree.dependencies.$bugid %]
           [% INCLUDE display_tree bugid=depid %]
         [% END %]
   [% global.seen.$bugid = 1 %]
 [% END %]
 
-[% BLOCK bullet %]
-  [% IF tree.dependencies.$bugid.size && ! global.seen.$bugid %]
-    [% extra_class = " b_open" %]
-    [% extra_args = 'onclick="return doToggle(this, event)"' %]
-  [% END %]
-  <a id="b[% bugid %]"
-     class="b [%+ extra_class FILTER none %]"
-     title="Click to expand or contract this portion of the tree. Hold down the Ctrl key while clicking to expand or contract all subtrees."
-     [% extra_args FILTER none %]>&nbsp;&nbsp;</a>
-[% END %]
-
 [% BLOCK buglink %]
-  [% isclosed = !bug.isopened %]
-  [% FILTER closed(isclosed) -%]
-    <a title="[% INCLUDE buginfo bug=bug %]"
-       href="[% basepath FILTER none %]show_bug.cgi?id=[% bugid %]">
-      <b>[%- bugid %]:</b>
-      <span class="summ_text">[%+ bug.short_desc FILTER html %]</span>
-      <span class="summ_info">[[% INCLUDE buginfo %]]</span>
-    </a>
-    <a href="[% basepath FILTER none %]showdependencytree.cgi?id=[% bugid FILTER uri %]"
-       class="tree_link">
-      <img src="[% basepath FILTER none %]skins/standard/dependency-tree/tree.png"
-           title="See dependency tree for [% terms.bug %] [%+ bugid FILTER html %]">
-    </a>
-  [% END %]
+  <a class="bug-link[% ' bz_closed' UNLESS bug.isopened %]"
+     href="[% basepath FILTER none %]show_bug.cgi?id=[% bugid %]" title="[% INCLUDE buginfo bug=bug %]">
+    <strong>[%- bugid %]:</strong>
+    <span class="summ_text">[%+ bug.short_desc FILTER html %]</span>
+    <span class="summ_info">[[% INCLUDE buginfo %]]</span>
+  </a>
+  <a class="tree-link iconic" href="[% basepath FILTER none %]showdependencytree.cgi?id=[% bugid FILTER uri %]"
+      title="See dependency tree for [% terms.Bug %] [%+ bugid FILTER html %]">
+    <span class="icon" aria-hidden="true"></span>
+  </a>
 [% END %]
 
 [% BLOCK buginfo %]
 [%###########################################################################%]
 
 [% BLOCK depthControlToolbar %]
- <table cellpadding="3" border="0" cellspacing="0" bgcolor="#e0e0e0">
+ <table cellpadding="3" border="0" cellspacing="0">
    <tr>
    [%# Hide/show resolved button
        Swaps text depending on the state of hide_resolved %]
      [%# set to one form %]
      <input type="submit" id="change_maxdepth"
        value="&nbsp;1&nbsp;"
-       [% "disabled" IF realdepth < 2 || maxdepth == 1 %]>
+       [% " disabled" IF realdepth < 2 || maxdepth == 1 %]>
      <input name="id" type="hidden" value="[% bugid %]">
      <input name="maxdepth" type="hidden" value="1">
      <input name="hide_resolved" type="hidden" value="[% hide_resolved %]">
      %]">
      <input name="hide_resolved" type="hidden" value="[% hide_resolved %]">
      <input type="submit" id="decrease_depth" value="&nbsp;&lt;&nbsp;"
-       [% "disabled" IF realdepth < 2 || ( maxdepth && maxdepth < 2 ) %]>
+       [% " disabled" IF realdepth < 2 || ( maxdepth && maxdepth < 2 ) %]>
    </form>
    </td>
 
      <input name="hide_resolved" type="hidden" value="[% hide_resolved %]">
      <noscript>
        <input type="submit" id="change_depth" value="Change"
-              [% "disabled" IF realdepth < 2 %]>
+              [% " disabled" IF realdepth < 2 %]>
      </noscript>
    </form>
    </td>
      [% END %]
      <input name="hide_resolved" type="hidden" value="[% hide_resolved %]">
      <input type="submit" id="increase_depth" value="&nbsp;&gt;&nbsp;"
-        [% "disabled" IF realdepth < 2 || !maxdepth || maxdepth >= realdepth %]>
+        [% " disabled" IF realdepth < 2 || !maxdepth || maxdepth >= realdepth %]>
    </form>
    </td>
 
      <input name="hide_resolved" type="hidden" value="[% hide_resolved %]">
      <input type="submit" id="remove_limit"
        value="&nbsp;Unlimited&nbsp;"
-       [% "disabled" IF maxdepth == 0 || maxdepth == realdepth %]>
+       [% " disabled" IF maxdepth == 0 || maxdepth == realdepth %]>
    </form>
    </td>
  </tr>