From 106b9212ce2942021c44f0562a154b6c65eb51af Mon Sep 17 00:00:00 2001 From: Kohei Yoshino Date: Mon, 20 May 2019 20:37:38 -0400 Subject: [PATCH] Bug 1542554 - Add bug type icons to dependency trees --- js/dependency-tree.js | 102 ++++++++++++ js/expanding-tree.js | 157 ------------------ skins/standard/dependency-tree.css | 107 +++++++----- .../en/default/bug/dependency-tree.html.tmpl | 81 ++++----- 4 files changed, 208 insertions(+), 239 deletions(-) create mode 100644 js/dependency-tree.js delete mode 100644 js/expanding-tree.js diff --git a/js/dependency-tree.js b/js/dependency-tree.js new file mode 100644 index 000000000..00b3c8ab6 --- /dev/null +++ b/js/dependency-tree.js @@ -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 index e86bff865..000000000 --- a/js/expanding-tree.js +++ /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 - * Christian Reis - * André Batosti - */ - -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); - } -} - diff --git a/skins/standard/dependency-tree.css b/skins/standard/dependency-tree.css index c22bf4313..ed3ff0f8d 100644 --- a/skins/standard/dependency-tree.css +++ b/skins/standard/dependency-tree.css @@ -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%; } diff --git a/template/en/default/bug/dependency-tree.html.tmpl b/template/en/default/bug/dependency-tree.html.tmpl index 06955e1bb..e91f64338 100644 --- a/template/en/default/bug/dependency-tree.html.tmpl +++ b/template/en/default/bug/dependency-tree.html.tmpl @@ -28,7 +28,7 @@ title = "Dependency tree for $terms.Bug $bugid" header = "Dependency tree for $terms.Bug $bugid" - 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" @@ -102,7 +102,7 @@ [% END %] [% END %] -
    +
      [% INCLUDE display_tree tree=$tree_name %]
    [% END %] @@ -115,22 +115,28 @@ # - tree: a hash of bug objects and of bug dependencies #%] [% bug = tree.$bugid %] -
  • - [%- INCLUDE bullet bugid=bugid tree=tree -%] - +
  • + [% IF tree.dependencies.$bugid.size && ! global.seen.$bugid %] + + [% END %] + + + + [%- INCLUDE buglink bug=bug bugid=bugid %] [% IF global.seen.$bugid %] - (*) + [% ELSIF tree.dependencies.$bugid.size %] -
      +
        [% FOREACH depid = tree.dependencies.$bugid %] [% INCLUDE display_tree bugid=depid %] [% END %] @@ -140,32 +146,17 @@ [% 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 %] -    -[% END %] - [% BLOCK buglink %] - [% isclosed = !bug.isopened %] - [% FILTER closed(isclosed) -%] - - [%- bugid %]: - [%+ bug.short_desc FILTER html %] - [[% INCLUDE buginfo %]] - - - - - [% END %] + + [%- bugid %]: + [%+ bug.short_desc FILTER html %] + [[% INCLUDE buginfo %]] + + + + [% END %] [% BLOCK buginfo %] @@ -179,7 +170,7 @@ [%###########################################################################%] [% BLOCK depthControlToolbar %] - +
        [%# Hide/show resolved button Swaps text depending on the state of hide_resolved %] @@ -210,7 +201,7 @@ [%# set to one form %] + [% " disabled" IF realdepth < 2 || maxdepth == 1 %]> @@ -229,7 +220,7 @@ %]"> + [% " disabled" IF realdepth < 2 || ( maxdepth && maxdepth < 2 ) %]> @@ -245,7 +236,7 @@ @@ -261,7 +252,7 @@ [% END %] = realdepth %]> + [% " disabled" IF realdepth < 2 || !maxdepth || maxdepth >= realdepth %]> @@ -273,7 +264,7 @@ + [% " disabled" IF maxdepth == 0 || maxdepth == realdepth %]> -- 2.47.3