Skip to content

Commit 939debf

Browse files
authored
Merge pull request #72 from nathancday/key_bindings
Add keyboard shortcuts
2 parents ef37e2b + ec26194 commit 939debf

File tree

5 files changed

+97
-21
lines changed

5 files changed

+97
-21
lines changed

NEWS.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11

22
# vdiffr 0.3.1.9000
33

4+
- Keyboard short cuts for common interactions:
5+
Right/Left arrows = Next/Previous case
6+
Down/Up arrows = Next/Previous type
7+
ENTER = Validate active case
8+
shift + ENTER = Validate active group
9+
ESC = Quit app
10+
411
# vdiffr 0.3.1
512

613
This release makes vdiffr compatible with ggplot2 3.2.0. It also

R/cases.R

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ delete_orphaned_cases <- function(cases = collect_orphaned_cases()) {
140140
walk(paths, file.remove)
141141
}
142142

143+
# It is brittle to keep `pkg_path` in attributes. Maybe change to an
144+
# R6 object?
143145
cases <- function(x, pkg_path, deps = NULL) {
144146
structure(x,
145147
class = "cases",

R/shiny-server.R

Lines changed: 48 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ vdiffrServer <- function(cases) {
33
shiny::shinyServer(function(input, output, session) {
44
cases <- shiny::reactiveValues(all = cases)
55
cases$active <- shiny::reactive({
6+
stopifnot(is_string(attr(cases$all, "pkg_path")))
67
type <- input$type %||% "new_case"
78
filter_cases(cases$all, type)
89
})
@@ -30,6 +31,7 @@ vdiffrServer <- function(cases) {
3031
output$case_context <- renderCaseContext(input, cases)
3132

3233
toggleValidateBtns(input, session)
34+
listenToKeys(input, session, cases)
3335

3436
quitApp(input)
3537
})
@@ -152,34 +154,33 @@ withdraw_cases <- function(cases) {
152154
}
153155

154156
validateSingleCase <- function(input, reactive_cases) {
155-
shiny::observe({
156-
if (input$case_validation_button > 0) {
157-
cases <- shiny::isolate(reactive_cases$all)
158-
case <- shiny::isolate(input$case)
157+
shiny::observeEvent(c(input$case_validation_button, input[["validateCase"]]), {
159158

160-
withdraw_cases(cases[case])
161-
cases[[case]] <- success_case(cases[[case]])
159+
cases <- shiny::isolate(reactive_cases$all)
160+
case <- shiny::isolate(input$case)
161+
shiny::req(input$case)
162162

163-
shiny::isolate(reactive_cases$all <- cases)
164-
}
163+
withdraw_cases(cases[case])
164+
cases[[case]] <- success_case(cases[[case]])
165+
166+
shiny::isolate(reactive_cases$all <- cases)
165167
})
166168
}
167169

168-
validateGroupCases <- function(input, reactive_cases) {
169-
shiny::observe({
170-
if (input$group_validation_button > 0) {
171-
active_cases <- shiny::isolate(reactive_cases$active())
172-
cases <- shiny::isolate(reactive_cases$all)
170+
validateGroupCases <- function(input, reactive_cases, session) {
171+
shiny::observeEvent(c(input$group_validation_button, input[["validateGroup"]]), {
172+
active_cases <- shiny::isolate(reactive_cases$active())
173+
cases <- shiny::isolate(reactive_cases$all)
173174

174-
if (length(cases) > 0) {
175-
type <- shiny::isolate(input$type)
175+
if (length(cases) > 0) {
176+
shiny::req(input$type)
177+
type <- shiny::isolate(input$type)
176178

177-
withdraw_cases(active_cases)
178-
idx <- sapply(cases, inherits, type)
179-
cases[idx] <- lapply(cases[idx], success_case)
179+
withdraw_cases(active_cases)
180+
idx <- purrr::map_lgl(cases, inherits, type)
181+
cases <- purrr::modify_if(cases, idx, success_case)
180182

181-
shiny::isolate(reactive_cases$all <- cases)
182-
}
183+
shiny::isolate(reactive_cases$all <- cases)
183184
}
184185
})
185186
}
@@ -230,6 +231,33 @@ toggleValidateBtns <- function(input, session) {
230231
})
231232
}
232233

234+
listenToKeys <- function(input, session, reactive_cases) {
235+
shiny::observeEvent(input[["nextCase"]], {
236+
names <- unique(names(reactive_cases$active()))
237+
shiny::updateSelectInput(session, "case",
238+
selected = next_element(input$case, names)
239+
)
240+
})
241+
shiny::observeEvent(input[["prevCase"]], {
242+
names <- unique(names(reactive_cases$active()))
243+
shiny::updateSelectInput(session, "case",
244+
selected = next_element(input$case, names, direction = -1)
245+
)
246+
})
247+
shiny::observeEvent(input[["nextType"]], {
248+
types <- unique(map_chr(reactive_cases$all, function(case) class(case)[[1]]))
249+
shiny::updateSelectInput(session, "type",
250+
selected = next_element(input$type, types)
251+
)
252+
})
253+
shiny::observeEvent(input[["prevType"]], {
254+
types <- unique(map_chr(reactive_cases$all, function(case) class(case)[[1]]))
255+
shiny::updateSelectInput(session, "type",
256+
selected = next_element(input$type, types, direction = -1)
257+
)
258+
})
259+
}
260+
233261
quitApp <- function(input) {
234262
shiny::observe({
235263
if (input$quit_button > 0) {

R/utils.R

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,3 +187,19 @@ is_ci <- function() {
187187
is_bool <- function(x) {
188188
is_logical(x, n = 1) && !is.na(x)
189189
}
190+
191+
next_element <- function(element, group, direction = 1) {
192+
if (element == "") {
193+
return(NULL) # if a type is empty
194+
}
195+
196+
next_position <- match(element, group) + direction
197+
198+
if (next_position > length(group)) {
199+
next_position <- next_position - length(group)
200+
} else if (next_position < 1) {
201+
next_position <- length(group)
202+
}
203+
204+
group[next_position]
205+
}

inst/www/toggle.js

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,27 @@ Shiny.addCustomMessageHandler('toggle-validate-btns-handler', toggleBtns);
1313
function toggleBtns(message) {
1414
document.getElementById('group_validation_button').disabled = message;
1515
document.getElementById('case_validation_button').disabled = message;
16-
}
16+
}
17+
18+
//Keyboard shortcuts
19+
document.onkeydown = function(e) {
20+
if (e.shiftKey) { // using e.shiftKey && e.which == 86 simultaneously doesn't work?
21+
if (e.which == 13) { // ENTER
22+
Shiny.onInputChange("validateGroup", Math.random());
23+
}
24+
} else {
25+
if (e.which == 39) { // right-arrow
26+
Shiny.onInputChange("nextCase", Math.random());
27+
} else if (e.which == 37) { // left-arrow
28+
Shiny.onInputChange("prevCase", Math.random());
29+
} else if (e.which == 40) { // down-arrow
30+
Shiny.onInputChange("nextType", Math.random());
31+
} else if (e.which == 38) { //up-arrow
32+
Shiny.onInputChange("prevType", Math.random());
33+
} else if (e.which == 13) { // ENTER
34+
Shiny.onInputChange("validateCase", Math.random());
35+
} else if (e.which == 27) { //esc, q = 81
36+
Shiny.onInputChange("quit_button", Math.random());
37+
}
38+
}
39+
};

0 commit comments

Comments
 (0)