server <- function(input, output, session) {
# Create reactive values container
r <- reactiveValues(
artist = NULL, # Will be set by artist selector
artwork = NULL, # Updated by browse module on card click
appdata = NULL # Populated by observer below
)
# Artist selection (from parent app logic)
observeEvent(input$artist_selector, {
r$artist <- input$artist_selector
})
# Load artist data when artist changes
observe({
req(r$artist)
r$appdata <- artutils::get_appdata(r$artist, r$artwork)
})
# Initialize browse module
modBrowseServer("browse", r)
}Overview
This guide covers the complete workflow for integrating modBrowse into a Shiny application, customizing its behavior, and extending its functionality. It assumes familiarity with Shiny modules and the Artalytics package ecosystem.
Integration Workflow
Step 1: Environment Setup
Before using modBrowse, ensure all required environment variables are set:
# Check database configuration
Sys.getenv("ART_PGHOST")
Sys.getenv("ART_PGPORT")
Sys.getenv("ART_PGUSER")
Sys.getenv("ART_PGPASS")
# Check CDN configuration
Sys.getenv("ART_BUCKETS_KEY_ID")
Sys.getenv("ART_BUCKETS_KEY_SECRET")Missing variables will cause {artcore} connection failures at runtime.
Step 2: Initialize Reactive Context
The browse module requires a reactive context with artist data:
Step 3: Wire Up Artwork Selection
When a user clicks an artwork card, modBrowse updates r$artwork. Use this to navigate to a detail view:
server <- function(input, output, session) {
r <- reactiveValues(...)
# React to artwork selection
observeEvent(r$artwork, {
req(r$artwork)
# Option 1: Navigate to gallery module
showModal(modalDialog(
modGalleryUI("gallery"),
size = "xl"
))
modGalleryServer("gallery", r)
# Option 2: Update URL for deep linking
updateQueryString(paste0("?artist=", r$artist, "&artwork=", r$artwork))
# Option 3: Switch tabs
updateTabsetPanel(session, "main_tabs", selected = "gallery")
})
modBrowseServer("browse", r)
}Filtering and Sorting
Available Filters
modBrowse provides four filter criteria:
| Filter | Column | Description |
|---|---|---|
| NFT Backed Art | is_nft |
Artworks with blockchain registration |
| Prints Available | is_print |
Artworks with print sales enabled |
| Listed For Sale | is_listed |
Artworks currently for sale |
| Variants Available | has_variants |
Artworks with multiple versions (n_variants > 1) |
Filters are applied as AND conditions - selecting multiple filters shows only artworks matching ALL selected criteria.
Sort Options
| Sort Key | Column | Description |
|---|---|---|
| Creation Date | created_utc |
When artwork was completed |
| Total Brush Strokes | brush_strokes |
Number of strokes in artwork |
| Total Drawing Time | drawing_hours |
Hours spent creating artwork |
Sort direction toggles between ascending and descending with the arrow button.
Desktop vs Mobile Sync
Desktop uses checkbox buttons; mobile uses a multi-select dropdown. These are synchronized bidirectionally using a syncing flag to prevent infinite loops. See app-server.R lines 321-403 for implementation.
Customizing Cards
Using artDisplayCard Directly
For custom browse layouts, use artDisplayCard() directly:
library(modBrowse)
# In a render function
output$custom_grid <- renderUI({
DT <- artwork_data()
cards <- lapply(seq_len(nrow(DT)), function(i) {
artDisplayCard(
ns = session$ns,
card_id = paste0("card_", i),
art_title = DT[i, art_title],
thumb_src = artutils::path_artwork_thumb(
artist = DT[i, artist_uuid],
artwork = DT[i, art_uuid]
),
is_new = DT[i, is_recent],
has_nft = DT[i, is_nft],
n_variants = DT[i, n_variants]
)
})
div(class = "row g-4", cards)
})Card Click Handler
Cards trigger input$card_clicked_ with the numeric suffix from card_id:
observeEvent(input$card_clicked_, {
card_num <- input$card_clicked_
selected_uuid <- artwork_data()[card_num, art_uuid]
# Handle selection...
})Statistics Display
Enhanced Stats Boxes
Use enhancedStatsUI() / enhancedStatsServer() for custom statistics:
# UI
fluidRow(
enhancedStatsUI(ns("metric1"),
label = "Custom Metric",
icon = "star",
color = "primary",
width = 4,
tooltip = "Description of this metric"
),
enhancedStatsUI(ns("metric2"),
label = "Another Metric",
icon = "chart-line",
color = "secondary",
width = 4
)
)
# Server
enhancedStatsServer("metric1", reactive({
scales::label_comma()(compute_metric_1())
}))
enhancedStatsServer("metric2", reactive({
paste0(compute_percent(), "%")
}))Collection Info Module
For a dedicated collection summary panel:
# UI
collectionInfoUI("collection_summary")
# Server
r_collect_name <- reactive({
names(r$appdata$artist$collections)[input$collection_idx]
})
collectionInfoServer("collection_summary", r_collect_name, r)Theming
Standalone Theme
When running run_app(), modBrowse applies its own theme via modBrowse_theme():
# View theme settings
modBrowse_theme()
# Returns bslib theme with:
# - Primary: #6366f1 (purple)
# - Secondary: #f59e0b (gold)
# - Rounded corners, subtle shadowsParent App Integration
When embedded in appPlatform, the parent’s theme takes precedence. modBrowse CSS uses CSS variables (var(--bs-primary), etc.) for theme compatibility.
Custom CSS
Override styles by adding CSS after including modBrowse:
ui <- page_fluid(
modBrowseUI("browse"),
tags$style(HTML("
.artwork-card { border-radius: 1rem; }
.enhanced-stats-box { background: linear-gradient(...); }
"))
)Testing
Unit Tests
Run the test suite:
devtools::test()Tests cover module structure and function signatures. Integration tests with mock reactive context are in tests/testthat/.
Manual Testing
Launch standalone app:
modBrowse::run_app(artist = "746b8207-72f5-4ab6-8d19-a91d03daec3d")Verify: - Collection selector populates with collections - Statistics update when collection changes - Filters correctly narrow artwork grid - Sort toggles work in both directions - Cards are clickable (check console for input$card_clicked_ values) - Dark mode toggle functions
Package Checks
Before committing:
devtools::document() # Regenerate docs
devtools::check() # Target: 0 errors, 0 warnings, 0 notes
lintr::lint_package() # Code styleTroubleshooting
Common Issues
“Cannot connect to database” - Check ART_PG* environment variables - Verify network access to database host
Cards show placeholder image - CDN credentials may be missing (ART_BUCKETS_*) - Artwork thumbnails may not exist yet
Statistics show em-dashes (—) - Collection may be empty - artDT columns may have unexpected NULL/NA values
Filters not applying - Check that filter columns exist in artDT - Verify column types are logical
Debug Mode
Enable Shiny debugging:
options(shiny.reactlog = TRUE)
shiny::reactlogShow() # After running appNext Steps
- Module Architecture - Internal design details
- Function Reference - Complete API
- Get Started - Quick overview
