Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose Inputs #337

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
2 changes: 2 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ S3method(ui_input,submit_field)
S3method(ui_input,switch_field)
S3method(ui_input,upload_field)
S3method(ui_input,variable_field)
S3method(ui_input_wrapper,default)
S3method(ui_input_wrapper,hidden_field)
S3method(ui_update,filesbrowser_field)
S3method(ui_update,hidden_field)
S3method(ui_update,list_field)
Expand Down
31 changes: 30 additions & 1 deletion R/field-core.R
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
title = title,
descr = descr,
status = status,
exclude = exclude
exclude = exclude,
always_show = FALSE
)
}

Expand Down Expand Up @@ -221,3 +222,31 @@
}
titles
}

observe_lock_field <- function(
x,
blk,
session = shiny::getDefaultReactiveDomain(),
...
) {
x |>
names() |>
lapply(\(field) {
id <- sprintf("%sLock", field)

observeEvent(session$input[[id]], {
# we can't change attributes on a reactive
# the attributes are present but attr(x, "name") <- 1
# causes an error
b <- as.list(blk())
attr(b[[field]], "always_show") <- session$input[[id]]
blk(b)

Check warning on line 243 in R/field-core.R

View check run for this annotation

Codecov / codecov/patch

R/field-core.R#L241-L243

Added lines #L241 - L243 were not covered by tests
})
})

return(blk)
}

get_field_always_show <- function(x) {
attr(x, "always_show")
}
14 changes: 14 additions & 0 deletions R/lock.R
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,17 @@
session$sendCustomMessage("lock", list(locked = session$userData$locked()))
}, ignoreInit = ignore_init)
}

lockInput <- function(inputId, locked = FALSE) {
icon <- icon("eye-slash")

if (locked)
icon <- icon("eye")

Check warning on line 88 in R/lock.R

View check run for this annotation

Codecov / codecov/patch

R/lock.R#L88

Added line #L88 was not covered by tests

tags$span(
id = inputId,
class = "lock-input small cursor-pointer text-muted position-absolute end-0",
`data-locked` = tolower(locked),
icon
)
}
3 changes: 3 additions & 0 deletions R/server.R
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,9 @@ generate_server_block <- function(x, in_dat = NULL, id, display = c("table", "pl
)
})

# this observes the lock input on the fields.
observe_lock_field(x, blk)

download(x, session, out_dat)

return(
Expand Down
26 changes: 25 additions & 1 deletion R/ui.R
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ ui_fields <- function(x, ...) {
#' @export
ui_fields.block <- function(x, ns, inputs_hidden, ...) {
fields <- Map(
ui_input,
ui_input_wrapper,
x,
id = chr_ply(names(x), ns),
name = get_field_names(x)
Expand Down Expand Up @@ -944,3 +944,27 @@ block_icon.plot_block <- function(x, ...) {
icon("chart-bar")
)
}

ui_input_wrapper <- function(x, id, name) {
UseMethod("ui_input_wrapper", x)
}

#' @export
ui_input_wrapper.default <- function(x, id, name) {
# Would be better to have the lockInput within the input label
# but even if we render this here all inputs labels are updated
# by update_field.
div(
class = "blockr-input-container position-relative",
lockInput(paste0(input_ids(x, id), "Lock"), get_field_always_show(x)),
div(
class = "blockr-input",
ui_input(x, id, name),
)
)
}

#' @export
ui_input_wrapper.hidden_field <- function(x, id, name) {
NULL
}
2 changes: 1 addition & 1 deletion inst/assets/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion inst/assets/style.min.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 37 additions & 0 deletions inst/examples/lock/app.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
devtools::load_all()

stack <- new_stack(data_block, select_block)
id <- "mystack"

shiny::shinyApp(
ui = bslib::page_fluid(
shiny::actionButton("lock", "Lock stacks"),
generate_ui(stack, id)
),
server = function(input, output, session) {
shiny::observeEvent(input$lock, {
lock()
})

vals <- reactiveValues(new_block = NULL)
stack <- generate_server(
stack,
id,
new_block = reactive(vals$new_block)
)

observeEvent(input$add, {
vals$new_block <- NULL
# Always append to stack
loc <- length(stack$blocks)
block <- available_blocks()[[input$selected_block]]
# add_block expect the current stack, the block to add and its position
# (NULL is fine for the position, in that case the block will
# go at the end)
vals$new_block <- list(
block = block,
position = loc
)
})
}
)
2 changes: 1 addition & 1 deletion makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ sass:
Rscript dev/sass.R

dev: bundle_dev
Rscript inst/examples/workspace/app.R
R -s -f inst/examples/lock/app.R

10 changes: 10 additions & 0 deletions scss/_block.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ html {
overflow-y: auto;
}

.lock-input{
opacity: 0;
}

.block {

.block-tools{
Expand All @@ -22,6 +26,12 @@ html {
border-radius: 0;
}

&:hover{
.lock-input{
opacity: 1;
}
}

.block-output-toggle {
color: var(--bs-gray);
position: absolute;
Expand Down
108 changes: 101 additions & 7 deletions srcjs/lock.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,7 @@ const handleLock = () => {
$(".stack-title").off();

$(".stack").each((_index, el) => {
const $editor = $(el).find(".stack-edit-toggle");
const isClosed = $editor.find("i").hasClass("fa-chevron-up");

if (isClosed) return;

$editor.trigger("click");
lock(el);
});
};

Expand All @@ -62,7 +57,106 @@ const lock = (stack) => {
const $editor = $stack.find(".stack-edit-toggle");
const isClosed = $editor.find("i").hasClass("fa-chevron-up");

moveInputs(stack);
if (isClosed) return;

$editor.trigger("click");
};

const moveInputs = (stack) => {
if ($(stack).find(".blockr-shown-inputs").length > 0) return;

$(stack)
.find(".card-body")
.first()
.prepend("<div class='blockr-shown-inputs p-2'></div>");

const $parent = $(stack).find(".card-body").find(".blockr-shown-inputs");

let n = 0;
$(stack)
.find(".block")
.each((_index, block) => {
const blockTitle = $(block)
.find(".block-title")
.find(".fw-bold")
.first()
.html();

const detached = [];
$(block)
.find(".lock-input[data-locked='true']")
.each((_index, el) => {
const input = $(el)
.closest(".blockr-input-container")
.find(".blockr-input")
.detach();

n++;
detached.push(input);
});

if (detached.length === 0) return;

$parent.append(`<h6 class="fw-bold">${blockTitle}</h6>`);

while (detached.length > 0) {
const rowItems = detached.splice(0, 4);

const row = document.createElement("div");
row.className = "row";

rowItems.map((el) => {
const col = document.createElement("div");
col.className = "col-3";
col.appendChild(el[0]);
row.appendChild(col);
});

$parent.append(row);
}
});

if (n > 0) return;

$(stack).find(".card-body").first().find(".blockr-shown-inputs").remove();
};

const toggleLock = (el) => {
const locked = el.attr("data-locked") === "true";
el.find("i").toggleClass("fa-eye fa-eye-slash");
el.attr("data-locked", String(!locked));
};

$(document).on("click", ".lock-input", function (event) {
const $el = $(event.currentTarget);
toggleLock($el);
$el.trigger("change");
});

const lockInput = new window.Shiny.InputBinding();

$.extend(lockInput, {
find: function (scope) {
return $(scope).find(".lock-input");
},
getValue: function (el) {
return $(el).attr("data-locked") === "true";
},
setValue: function (el, value) {
toggleLock(el);
$(el).attr("data-locked", value);
},
receiveMessage: function (el, value) {
this.setValue(el, value);
},
subscribe: function (el, callback) {
$(el).on("change.lock-input", function () {
callback(true);
});
},
unsubscribe: function (el) {
$(el).off(".lock-input");
},
});

window.Shiny.inputBindings.register(lockInput, "blockr.lockInput");
Loading