blob: 9a75971290681b17680d982ffb2d05fe5ff61b0b [file] [log] [blame]
Copybara854996b2021-09-07 19:36:02 +00001/* Copyright 2016 The Chromium Authors. All Rights Reserved.
2 *
3 * Use of this source code is governed by a BSD-style
4 * license that can be found in the LICENSE file or at
5 * https://developers.google.com/open-source/licenses/bsd
6 */
7
8/**
9 * This file contains JS functions that implement keystroke accelerators
10 * for Monorail.
11 */
12
13/**
14 * Array of HTML elements where the kibbles cursor can be. E.g.,
15 * the TR elements of an issue list, or the TR's for comments on an issue.
16 */
17let TKR_cursorStops;
18
19/**
20 * Integer index into TKR_cursorStops of the currently selected cursor
21 * stop, or undefined if nothing has been selected yet.
22 */
23let TKR_selected = undefined;
24
25/**
26 * Register keystrokes that apply to all pages in the current component.
27 * E.g., keystrokes that should work on every page under the "Issues" tab.
28 * @param {string} listUrl Rooted URL of the artifact list.
29 * @param {string} entryUrl Rooted URL of the artifact entry page.
30 * @param {string} currentPageType One of 'list', 'entry', or 'detail'.
31 */
32function TKR_setupKibblesComponentKeys(listUrl, entryUrl, currentPageType) {
33 if (currentPageType != 'list') {
34 kibbles.keys.addKeyPressListener(
35 'u', function() {
36 TKR_go(listUrl);
37 });
38 }
39}
40
41
42/**
43 * On the artifact list page, go to the artifact at the kibbles cursor.
44 * @param {number} linkCellIndex row child that is expected to hold a link.
45 */
46function TKR_openArtifactAtCursor(linkCellIndex, newWindow) {
47 if (TKR_selected >= 0 && TKR_selected < TKR_cursorStops.length) {
48 window._goIssue(TKR_selected, newWindow);
49 }
50}
51
52
53/**
54 * On the artifact list page, toggle the checkbox for the artifact at
55 * the kibbles cursor.
56 * @param {number} cbCellIndex row child that is expected to hold a checkbox.
57 */
58function TKR_selectArtifactAtCursor(cbCellIndex) {
59 if (TKR_selected >= 0 && TKR_selected < TKR_cursorStops.length) {
60 const cell = TKR_cursorStops[TKR_selected].children[cbCellIndex];
61 let cb = cell.firstChild;
62 while (cb && cb.tagName != 'INPUT') {
63 cb = cb.nextSibling;
64 }
65 if (cb) {
66 cb.checked = cb.checked ? '' : 'checked';
67 TKR_highlightRow(cb);
68 }
69 }
70}
71
72/**
73 * On the artifact list page, toggle the star for the artifact at
74 * the kibbles cursor.
75 * @param {number} cbCellIndex row child that is expected to hold a checkbox
76 * and star widget.
77 */
78function TKR_toggleStarArtifactAtCursor(cbCellIndex) {
79 if (TKR_selected >= 0 && TKR_selected < TKR_cursorStops.length) {
80 const cell = TKR_cursorStops[TKR_selected].children[cbCellIndex];
81 let starIcon = cell.firstChild;
82 while (starIcon && starIcon.tagName != 'A') {
83 starIcon = starIcon.nextSibling;
84 }
85 if (starIcon) {
86 _TKR_toggleStar(
87 starIcon, issueRefs[TKR_selected]['project_name'],
88 issueRefs[TKR_selected]['id'], null, null);
89 }
90 }
91}
92
93/**
94 * Updates the style on new stop and clears the style on the former stop.
95 * @param {Object} newStop the cursor stop that the user is selecting now.
96 * @param {Object} formerStop the old cursor stop, if any.
97 */
98function TKR_updateCursor(newStop, formerStop) {
99 TKR_selected = undefined;
100 if (formerStop) {
101 formerStop.element.classList.remove('cursor_on');
102 formerStop.element.classList.add('cursor_off');
103 }
104 if (newStop && newStop.element) {
105 newStop.element.classList.remove('cursor_off');
106 newStop.element.classList.add('cursor_on');
107 TKR_selected = newStop.index;
108 }
109}
110
111
112/**
113 * Walk part of the page DOM to find elements that should be kibbles
114 * cursor stops. E.g., the rows of the issue list results table.
115 * @return {Array} an array of html elements.
116 */
117function TKR_findCursorRows() {
118 const rows = [];
119 const cursorarea = document.getElementById('cursorarea');
120 TKR_accumulateCursorRows(cursorarea, rows);
121 return rows;
122}
123
124
125/**
126 * Recusrively walk part of the page DOM to find elements that should
127 * be kibbles cursor stops. E.g., the rows of the issue list results
128 * table. The cursor stops are appended to the given rows array.
129 * @param {Element} parent html element to start on.
130 * @param {Array} rows array of html TR or DIV elements, each cursor stop will
131 * be added to this array.
132 */
133function TKR_accumulateCursorRows(parent, rows) {
134 for (let i = 0; i < parent.childNodes.length; i++) {
135 const elem = parent.childNodes[i];
136 const name = elem.tagName;
137 if (name && (name == 'TR' || name == 'DIV')) {
138 if (elem.className.indexOf('cursor') >= 0) {
139 elem.cursorIndex = rows.length;
140 rows.push(elem);
141 }
142 }
143 TKR_accumulateCursorRows(elem, rows);
144 }
145}
146
147
148/**
149 * Initialize kibbles cursors stops for the current page.
150 * @param {boolean} selectFirstStop True if the first stop should be
151 * selected before the user presses any keys.
152 */
153function TKR_setupKibblesCursorStops(selectFirstStop) {
154 kibbles.skipper.addStopListener(
155 kibbles.skipper.LISTENER_TYPE.PRE, TKR_updateCursor);
156
157 // Set the 'offset' option to return the middle of the client area
158 // an option can be a static value, or a callback
159 kibbles.skipper.setOption('padding_top', 50);
160
161 // Set the 'offset' option to return the middle of the client area
162 // an option can be a static value, or a callback
163 kibbles.skipper.setOption('padding_bottom', 50);
164
165 // register our stops with skipper
166 TKR_cursorStops = TKR_findCursorRows();
167 for (let i = 0; i < TKR_cursorStops.length; i++) {
168 const element = TKR_cursorStops[i];
169 kibbles.skipper.append(element);
170
171 if (element.className.indexOf('cursor_on') >= 0) {
172 kibbles.skipper.setCurrentStop(i);
173 }
174 }
175}
176
177
178/**
179 * Initialize kibbles keystrokes for an artifact entry page.
180 * @param {string} listUrl Rooted URL of the artifact list.
181 * @param {string} entryUrl Rooted URL of the artifact entry page.
182 */
183function TKR_setupKibblesOnEntryPage(listUrl, entryUrl) {
184 TKR_setupKibblesComponentKeys(listUrl, entryUrl, 'entry');
185}
186
187
188/**
189 * Initialize kibbles keystrokes for an artifact list page.
190 * @param {string} listUrl Rooted URL of the artifact list.
191 * @param {string} entryUrl Rooted URL of the artifact entry page.
192 * @param {string} projectName Name of the current project.
193 * @param {number} linkCellIndex table column that is expected to
194 * link to individual artifacts.
195 * @param {number} opt_checkboxCellIndex table column that is expected
196 * to contain a selection checkbox.
197 */
198function TKR_setupKibblesOnListPage(
199 listUrl, entryUrl, projectName, linkCellIndex,
200 opt_checkboxCellIndex) {
201 TKR_setupKibblesCursorStops(true);
202
203 kibbles.skipper.addFwdKey('j');
204 kibbles.skipper.addRevKey('k');
205
206 if (opt_checkboxCellIndex != undefined) {
207 const cbCellIndex = opt_checkboxCellIndex;
208 kibbles.keys.addKeyPressListener(
209 'x', function() {
210 TKR_selectArtifactAtCursor(cbCellIndex);
211 });
212 kibbles.keys.addKeyPressListener(
213 's',
214 function() {
215 TKR_toggleStarArtifactAtCursor(cbCellIndex);
216 });
217 }
218 kibbles.keys.addKeyPressListener(
219 'o', function() {
220 TKR_openArtifactAtCursor(linkCellIndex, false);
221 });
222 kibbles.keys.addKeyPressListener(
223 'O', function() {
224 TKR_openArtifactAtCursor(linkCellIndex, true);
225 });
226 kibbles.keys.addKeyPressListener(
227 'enter', function() {
228 TKR_openArtifactAtCursor(linkCellIndex);
229 });
230
231 TKR_setupKibblesComponentKeys(listUrl, entryUrl, 'list');
232}