rewind now captures only inputs
that the page can put back. A plot’s hover,
click, dblclick and brush, and
the values that DT keeps, such as _rows_current and
_state, have no element in the page to restore them.
rewind recorded them anyway, so each move of the mouse over
a plot was a step, and an undo of it seemed to do nothing. The browser
now reports which inputs have a Shiny binding, and only those are
captured. A list in inputs is used as it is, so name an
input there to capture it anyway. Until the report arrives, and in
testServer(), every input is captured as before.rewind_status() lists what rewind
captures, and gives each other input the reason it is left out:
excluded, a password field, not in inputs, cannot be
restored by the page, and so on. It answers “why does an undo skip this
input?” in one call.rewind_exclude() leaves inputs out of the history from
anywhere in the server, a module included, and at any time. It works
like shiny::setBookmarkExclude(): inside a module, give the
module’s own names. A second call adds to the list.rewind_unrecorded() runs a block of code whose changes
are not a step. They become part of the current state, so an undo never
goes back across them. Use it for code that restores a saved view or
applies a bookmark. It is the other side of
rewind_step().rewind_enable(labels = ) gives the names a person
reads, such as c(region = "Region"). The history rail shows
them, and rewind_diff() has a new label
column. It is the last column, so code that picks the six columns of
0.3.0 by position still gets the same ones.rewind_example() runs the demo application. It replaces
shiny::runApp(system.file("examples/demo", package = "rewind")).The help of rewind_track() has a new section on
environments and R6 objects. rewind keeps such a value by
reference, so a change inside the object is not a step, and an undo
cannot bring back the old contents. The section shows how to change a
copy instead.
A pause and a resume in the same observer had no effect, although
the help of rewind_pause() suggested exactly that, around
code that applies a bookmark. rewind captures in its own
observer, which runs after the one that made the change. The help now
says so, and points to rewind_unrecorded().
Every argument is now checked, with a message that names it.
These were accepted before: shortcuts = "yes" (which
switched the shortcuts off), depth = 2.5 (which became 2),
rewind_jump(1.9) (which moved to position 1),
rewind_step(label = 1:2), and
rewind_ui(max_height = 300), which wrote CSS that browsers
ignore. A number is now pixels there, as in shiny and
bslib.
A name in inputs that the page does not have, and a
name in rewind_track(fields = ) that the reactive values do
not have, now give a warning. A typo used to capture nothing, with no
message.
An input that changes all the time no longer holds back every step. The grouping period started again whenever any input changed, also one that is not captured, so a plot hover held every step back until the mouse stopped, and merged the changes in between.
rewind_enable(c("region", "year")) gave the names to
session, the first argument, and failed with “$ operator is
invalid for atomic vectors”. It now says that session must
be a Shiny session, and points to inputs = c(...).
Password fields are never kept in the history. A
passwordInput() sends a plain string, so
rewind captured it like any text box: the password was kept
in the history, put back in the field by an undo, and shown in plain
text by rewind_diff(). The browser now reports which fields
are passwords, including fields that renderUI() adds later,
and rewind leaves them out, even when inputs
names them. A value captured before the report arrives is removed from
the history.
rewind_diff() shows an empty text box as
"". A change from an empty box read
-> hello, with nothing before the arrow.
The help page of rewind_enable() has a new section
on text inputs: slow typing can give several steps,
textInput(updateOn = "blur") gives one step for each edit,
and Ctrl + Z inside a text box is the
browser’s own undo, which rewind then records as a new
step.
The fallback in rewind.js that waits for Shiny to
connect used addEventListener(), which never receives the
jQuery event that Shiny triggers. It now uses jQuery. The fallback is
not used when Shiny loads first, which is the usual order.
This release goes to CRAN. Version 0.2.1 was released on GitHub only, so an update from 0.2.0 on CRAN brings the changes of 0.2.1 below as well.
New rewind_diff(). It shows what changed between two
steps, and not only which names changed. [rewind_history()] gives a
label such as "region, year"; rewind_diff()
gives one row for each value, with the value before, the value after,
and a short description such as "North -> South". The
old and new columns are list columns, so a
value of any type survives, including a date range, a multiple selection
or a whole data frame. The result depends on the history, so it works
inside render*() and observe().
It is not an audit trail, and the documents say so. The history is in
the memory of one session, it drops the oldest entries when it passes
depth, and it drops the steps in front of the position
after a change. Write a record for an inspection as each change occurs,
and keep it elsewhere.
The history rail no longer scrolls the page or the sidebar that
holds it. It kept the current step in view with
scrollIntoView(), which scrolls every scrollable ancestor
of the element and not only the nearest one. In a layout that puts
rewind_ui() in a sidebar, every change to the history
scrolled that sidebar, so the application appeared to scroll by itself.
The rail now sets its own scrollTop, and moves nothing
else.
Fixed: undo, redo and the rail did nothing when
rewind_enable() was called inside a
moduleServer(). The browser sends the global input ids
rewind_undo, rewind_redo and
rewind_jump, but inside a module
rewind_enable() watched the namespaced ids. Capture still
worked, so the rail filled up, but no button, shortcut or rail click
reached the history. rewind_enable() now reads these inputs
from the root session.
Fixed: the rail showed each time in UTC, and not in the viewer’s
time zone. The server now sends the moment, and the browser formats it
with its own clock. The format is still HH:MM:SS. The
time column of rewind_history() is
unchanged.
rewind_enable() and rewind_step() now
refuse NA, NaN and Inf in
coalesce_ms, restore_timeout and
hold_ms, with a message that names the argument.
NA gave “missing value where TRUE/FALSE needed”.
Inf was accepted and then did harm with no message: in
coalesce_ms it stopped the history from recording anything.
hold_ms was not checked at all before.
rewind_diff() now refuses a position that is not a
whole number, such as 2.7. It truncated it to
2 before.
rewind_enable() takes a restore_timeout
argument, in seconds. It sets how long rewind waits for the
browser to finish a restore. The value was fixed at 2 seconds before,
which is enough on a fast connection but not always on a slow one. If
the limit is too short, capture starts again while the browser is still
applying the restore, and a partial state becomes a history entry that
the user never made.rewind no longer drops a data frame of the user that
has the same four column names as the value of a
fileInput(). It now examines the column types as well as
the names. Such a value was dropped from the history before, with no
message.rewind_buttons() takes a button_class
argument. It adds CSS classes to the two buttons. Use it for a Bootstrap
variant such as "btn-primary" or
"btn-outline-secondary", and for a size such as
"btn-sm". The class argument continues to hold
classes for the container element only, so there was no way to reach the
buttons before.↶ and ↷, which come from
the font of the browser and thus have a different weight and size on
each system. The SVG arrows use currentColor and a size in
em. They thus follow the colour and the size of the button,
including with btn-sm, btn-lg, an outline
variant and a dark theme.Ctrl + Y has always done a redo, but the
button did not say so.shortcuts argument of
rewind_enable() now lists the three shortcuts, and says
which one belongs to which system.rewind_disable() stops undo and redo for a session
completely. It destroys each observer that rewind_enable()
made. It also clears the buttons and the rail in the browser. Use
rewind_pause() and rewind_resume() to stop
capture for a short time. Use rewind_disable() to stop it
for the rest of the session.rewind no longer captures fileInput(). Its
value points to a temporary file on the server. Shiny deletes that file
at the next upload. An old snapshot would thus point to a file that does
not exist.rewind_pause() before the first
reactive flush stopped capture for the rest of the session. A later
rewind_resume() did not start it again. This occurred, for
example, with a call to rewind_pause() immediately after
rewind_enable(), before any input changed.rewind_buttons(undo_label = NULL, redo_label = NULL)
now sets aria-label on the buttons. A button with an icon
and no text thus has a correct accessible name.rewind_enable() inside a moduleServer()
now has tests. The documents said “untested” before.First release.
rewind_enable() captures the session inputs into an
undo and redo history. It groups the changes by time. One movement of a
slider is thus one step, and not forty steps.rewind_track() adds the reactiveValues on
the server to the history.rewind_step() puts several changes into one entry with
a label.rewind_buttons() and rewind_ui() give
ready-made undo and redo controls, and a scrubbable history rail.Ctrl/Cmd +
Z, Ctrl/Cmd + Shift
+ Z, and Ctrl + Y. They do
nothing while the user types in a text field.update*Input() table. Inputs from other packages thus
operate with no extra code.