Skip to content

Commit 6fa4a0f

Browse files
authored
feat(srcts): Show keyboard shortcuts in a popup (#73) (#158)
Add a ? button to the header that lists every keyboard shortcut on hover or click, with key names for the user's OS. List the same shortcuts in the vignette.
1 parent b5520ac commit 6fa4a0f

8 files changed

Lines changed: 195 additions & 9 deletions

File tree

‎NEWS.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
- Migrated project license from GPL-3 to the MIT License.
88

9+
- Added a `?` button to the reactlog header. Hover over it or click it to see every keyboard shortcut, with key names for your operating system. The navigation button tooltips now also use your operating system's key names. The vignette has the same list (#73)
10+
911
- Documented that `reactlog_show()` passes `...` on to `reactlog_render()`, so `session_token` can be set from `reactlog_show()`. The docs used to say `...` was ignored (#120)
1012

1113
- Fixed `reactlog_render()` printing "Loading required namespace: shiny" (#120)

‎inst/reactlog/reactlog.html‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@
4646
<div id="filter">
4747
<input id="search" type="text" size="12" placeholder="Search Labels..."/>
4848
</div>
49+
<div id="shortcuts">
50+
<button id="shortcutsButton" type="button" aria-label="Keyboard shortcuts" aria-expanded="false" aria-controls="shortcutsPopup">?</button>
51+
<div id="shortcutsPopup"></div>
52+
</div>
4953
</div> <!-- end statusHeader -->
5054

5155
<div id="timeline" style="z-index:1;">

‎inst/reactlog/reactlogAsset/reactlog.css‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,54 @@ text {
197197
margin-left: 5px;
198198
margin-right: 5px;
199199
}
200+
#shortcuts {
201+
position: relative;
202+
margin-right: 8px;
203+
}
204+
#shortcutsButton {
205+
display: block;
206+
width: 16px;
207+
height: 16px;
208+
padding: 0;
209+
border: 1px solid #d0d0d0;
210+
border-radius: 50%;
211+
background: transparent;
212+
color: #a0a0a0;
213+
font-family: inherit;
214+
font-size: 11px;
215+
line-height: 14px;
216+
cursor: pointer;
217+
}
218+
#shortcutsButton:hover,
219+
#shortcuts.open #shortcutsButton {
220+
border-color: #a0a0a0;
221+
color: #606060;
222+
}
223+
#shortcutsPopup {
224+
display: none;
225+
position: absolute;
226+
top: 100%;
227+
right: 0;
228+
z-index: 2000;
229+
padding: 8px 12px;
230+
background: #fff;
231+
border: 1px solid #d0d0d0;
232+
border-radius: 5px;
233+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
234+
font-size: 12px;
235+
}
236+
#shortcuts:hover #shortcutsPopup,
237+
#shortcuts.open #shortcutsPopup {
238+
display: block;
239+
}
240+
#shortcutsPopup th {
241+
text-align: left;
242+
font-weight: 600;
243+
padding: 8px 0 4px;
244+
}
245+
#shortcutsPopup td {
246+
padding: 2px 12px 2px 0;
247+
}
200248
#search {
201249
background: url("images/iconSearch.svg") no-repeat scroll 0 0;
202250
background-size: 20px 20px;

‎inst/reactlog/reactlogAsset/reactlog.js‎

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎inst/reactlog/reactlogAsset/reactlog.js.map‎

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎srcts/index.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import colors from "./style/colors";
1212
import * as cytoscapeInit from "./cyto/cytoscapeInit";
1313

1414
import * as layoutKeydown from "./layout/keydown";
15+
import * as shortcuts from "./layout/shortcuts";
1516

1617
import * as updateGraph from "./updateGraph";
1718

@@ -128,6 +129,7 @@ $(function () {
128129
);
129130

130131
updateGraph.searchStringContainer($("#search"));
132+
shortcuts.setup();
131133

132134
{
133135
let docBody = document.body;

‎srcts/layout/shortcuts.ts‎

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import $ from "jquery";
2+
3+
const isMac = /Mac|iPhone|iPad/.test(navigator.userAgent);
4+
5+
const alt = isMac ? "Option" : "Alt";
6+
const shift = "Shift";
7+
const ctrl = isMac ? "Cmd" : "Ctrl";
8+
// Mac laptops have no Home / End keys
9+
const home = isMac ? ["Fn", "←"] : ["Home"];
10+
const end = isMac ? ["Fn", "→"] : ["End"];
11+
12+
let comboText = function (keys: string[]): string {
13+
return keys.join(" + ");
14+
};
15+
16+
// each row: alternative key combos, then the action
17+
type Row = [string[][], string];
18+
19+
const reactlogRows: Row[] = [
20+
[[["→"], ["Space"]], "Next step"],
21+
[[["←"]], "Previous step"],
22+
[
23+
[
24+
[shift, "←"],
25+
[shift, "→"],
26+
],
27+
"Previous / next log entry",
28+
],
29+
[
30+
[
31+
[alt, "←"],
32+
[alt, "→"],
33+
],
34+
"Previous / next output calculation",
35+
],
36+
[
37+
[
38+
[alt, shift, "←"],
39+
[alt, shift, "→"],
40+
],
41+
"Previous / next idle",
42+
],
43+
[[home, end], "Previous / next user mark"],
44+
[[["s"]], "Focus the search bar"],
45+
[[["Esc"]], "Leave search; clear highlight and filter"],
46+
[[["f"]], "Fit the graph to the window"],
47+
[[["l"]], "Show / hide log entry details"],
48+
];
49+
const shinyRows: Row[] = [
50+
[[[ctrl, "F3"]], "Open reactlog"],
51+
[
52+
[
53+
[ctrl, shift, "F3"],
54+
[ctrl, "F4"],
55+
],
56+
"Add a user mark",
57+
],
58+
];
59+
60+
let sectionHtml = function (title: string, rows: Row[]): string {
61+
let body = rows
62+
.map(
63+
([combos, action]) =>
64+
`<tr><td class="monospaced-pre">${combos.map(comboText).join(" / ")}</td><td>${action}</td></tr>`
65+
)
66+
.join("");
67+
return `<tr><th colspan="2">${title}</th></tr>${body}`;
68+
};
69+
70+
let setup = function (): void {
71+
$("#shortcutsPopup").html(
72+
"<table>" +
73+
sectionHtml("Keyboard shortcuts", reactlogRows) +
74+
sectionHtml("In the Shiny app", shinyRows) +
75+
"</table>"
76+
);
77+
78+
let wrapper = $("#shortcuts");
79+
$("#shortcutsButton").on("click", function () {
80+
wrapper.toggleClass("open");
81+
$(this).attr("aria-expanded", String(wrapper.hasClass("open")));
82+
// keep focus off the button so Space / arrow keys still move the graph
83+
$(this).trigger("blur");
84+
});
85+
86+
// navigation button tooltips
87+
let titles: [string, string, string[]][] = [
88+
["prevUserMarkButton", "Previous User Mark", home],
89+
["prevIdleButton", "Previous idle", [alt, shift, "←"]],
90+
["prevOutputCalcButton", "Previous output calculation", [alt, "←"]],
91+
["prevStepButton", "Previous step", ["←"]],
92+
["nextStepButton", "Next step", ["→"]],
93+
["nextOutputCalcButton", "Next output calculation", [alt, "→"]],
94+
["nextIdleButton", "Next idle", [alt, shift, "→"]],
95+
["nextUserMarkButton", "Next User Mark", end],
96+
];
97+
titles.forEach(([id, label, keys]) => {
98+
$(`#${id}`).attr("title", `${label} (${comboText(keys)})`);
99+
});
100+
};
101+
102+
export { setup };

‎vignettes/reactlog.Rmd‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,8 @@ knitr::include_graphics("images/navigation.png")
276276
277277
> Tip: Hold `SHIFT` with the `Left/Right Arrow keys` to move one log entry at a time, including entries that are not shown as their own step.
278278
279+
> All key bindings are listed in [Keyboard shortcuts](#keyboard-shortcuts).
280+
279281
##### Search Bar
280282

281283
The search bar matches the name or ID of each reactive graph element. All matching elements are increased in size to signify a match and all matching elements' family trees are displayed. The search becomes active after three characters, and if no matches are found, the full graph is displayed.
@@ -349,6 +351,32 @@ knitr::include_graphics("images/elements.png")
349351

350352
Fortunately, **shiny**'s `reactive()` and `observe()` functions have a `label` argument. If provided, **reactlog** will use this value to label the corresponding nodes.
351353

354+
### Keyboard shortcuts {#keyboard-shortcuts}
355+
356+
Hover over or click the `?` button at the top right of **reactlog** to see these shortcuts with the key names for your computer.
357+
358+
Inside the **shiny** application (Mac users: use `Cmd` in place of `Ctrl`):
359+
360+
| Keys | Action |
361+
|------|--------|
362+
| `Ctrl + F3` | Open **reactlog** |
363+
| `Ctrl + Shift + F3` or `Ctrl + F4` | Add a user mark at the current time |
364+
365+
Inside **reactlog** (`Alt` is `Option` on a Mac):
366+
367+
| Keys | Action |
368+
|------|--------|
369+
| `Right Arrow` or `Space` | Next step |
370+
| `Left Arrow` | Previous step |
371+
| `Shift + Left/Right Arrow` | Previous / next log entry, including entries that are not shown as their own step |
372+
| `Alt + Left/Right Arrow` | Previous / next reactive endpoint calculation |
373+
| `Alt + Shift + Left/Right Arrow` | Previous / next idle step |
374+
| `Home` / `End` | Previous / next user mark (or the start / end of the log) |
375+
| `s` | Focus the search bar |
376+
| `Esc` | Leave the search bar; otherwise clear the highlight and filter |
377+
| `f` | Fit the graph to the window |
378+
| `l` | Show or hide detailed information about the current step |
379+
352380
## Learn more {#learn-more}
353381

354382
This vignette explains the **reactlog** interface, which requires a basic understanding of **shiny** and reactive programming. There are many other useful existing articles and resources that may help advance your understanding of **reactlog** and **shiny**, including but not limited to:

0 commit comments

Comments
 (0)