The hardware and bandwidth for this mirror is donated by METANET, the Webhosting and Full Service-Cloud Provider.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]metanet.ch.

Coming from Shiny

# Convert a data frame for JSON serialisation
result <- RDesk::rdesk_df_to_list(head(mtcars, 3))
length(result$rows)   # 3
#> [1] 3
result$cols[1:3]      # first three column names
#> [1] "mpg"  "cyl"  "disp"

If you know Shiny, you can learn RDesk in an afternoon. Your R data logic moves across unchanged. What changes is the delivery layer – instead of a browser, you get a native Windows window.

The mental model shift

Shiny uses a reactive graph – inputs change, outputs update automatically. RDesk uses explicit message passing – the UI sends a message, R handles it and pushes a result back.

This feels more like writing an API handler than a Shiny server.

Side-by-side patterns

Responding to user input

Shiny

server <- function(input, output, session) {
  output$plot <- renderPlot({
    filtered <- mtcars[mtcars$cyl == input$cyl, ]
    plot(filtered$wt, filtered$mpg)
  })
}

RDesk

app$on_message("filter", async(function(payload) {
  filtered <- mtcars[mtcars$cyl == payload$cyl, ]
  list(chart = rdesk_plot_to_base64(
    plot(filtered$wt, filtered$mpg)
  ))
}, app = app))

The key difference: RDesk does not re-run automatically. The UI explicitly calls rdesk.send("filter", {cyl: 6}) and receives filter_result back.

Sending data to the UI

Shiny

output$table <- renderTable({ mtcars })

RDesk

# In R -- push data explicitly
app$send("table_data", list(
  rows = lapply(seq_len(nrow(mtcars)), function(i) as.list(mtcars[i,])),
  cols = names(mtcars)
))
// In JavaScript -- receive it
rdesk.on("table_data", function(data) {
  renderTable(data.rows, data.cols);
});

File downloads

Shiny

output$download <- downloadHandler(
  filename = "data.csv",
  content  = function(file) write.csv(mtcars, file)
)

RDesk

app$on_message("save_csv", function(payload) {
  path <- app$dialog_save(
    title   = "Save CSV",
    filters = "CSV files (*.csv)|*.csv"
  )
  if (!is.null(path)) write.csv(mtcars, path, row.names = FALSE)
})

Native menus (no Shiny equivalent)

app$set_menu(list(
  File = list(
    "Open..."  = function() app$dialog_open(),
    "Save..."  = function() app$dialog_save(),
    "---",
    "Exit"     = app$quit
  ),
  Help = list(
    "About"    = function() app$toast("MyApp v1.0", type = "info")
  )
))

Async processing

Shiny (with future/promises)

library(future)
plan(multisession)

observeEvent(input$run, {
  future({
    slow_model(input$data)
  }) %...>% (function(result) {
    output$result <- renderText(result)
  })
})

RDesk

app$on_message("run_model", async(function(payload) {
  slow_model(payload$data)
}, app = app, loading_message = "Running model..."))

RDesk’s async() handles the loading overlay, cancellation, and result routing automatically.

What you can reuse unchanged

Everything that does not touch Shiny inputs/outputs moves across directly:

What you must rewrite

Shiny pattern RDesk equivalent
input$x payload$x inside on_message()
reactive({...}) Call helper functions explicitly
renderPlot({...}) rdesk_plot_to_base64() + app$send()
renderTable({...}) app$send() with list of rows
observe({...}) app$on_message() handler
showNotification() app$toast()

Practical migration path

  1. Keep all your data and modelling R files unchanged
  2. Replace ui.R with www/index.html (plain HTML – no Shiny DSL)
  3. Replace server.R handlers one by one using app$on_message()
  4. Replace renderPlot calls with rdesk_plot_to_base64() + app$send()
  5. Run source("app.R") and test each handler as you migrate

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.