diff --git a/DESCRIPTION b/DESCRIPTION index 5f16934..6d0ca2a 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: css2r Title: An Amazing Shiny App -Version: 0.1.0 +Version: 0.2.0 Authors@R: person("Arthur", "Bréant", , "arthur@thinkr.fr", role = c("aut", "cre")) Description: What the package does (one paragraph). diff --git a/NEWS.md b/NEWS.md index 25f14f9..d6845ff 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,5 @@ +# css2r 0.2.0 + # css2r 0.1.0 * Update UI with new features diff --git a/R/app_server.R b/R/app_server.R index ff2aed2..afabaf1 100644 --- a/R/app_server.R +++ b/R/app_server.R @@ -5,7 +5,7 @@ #' @import shiny #' @noRd app_server <- function(input, output, session) { - rv <- reactiveValues(site = NULL) + rv <- reactiveValues(site = NULL, preview_primary = NULL, preview_secondary = NULL) observeEvent(input$analyze_btn, { rv$result <- NULL @@ -66,6 +66,12 @@ app_server <- function(input, output, session) { observeEvent(rv$continue, { req(rv$site) + session$setCurrentTheme(rv$site$shiny_theme) + + top_colors_df_init <- rv$site$top_colors$top_colors + rv$preview_primary <- top_colors_df_init$Color[1] + rv$preview_secondary <- if (nrow(top_colors_df_init) > 1) top_colors_df_init$Color[2] else top_colors_df_init$Color[1] + session$sendCustomMessage( type = "apply_gradient", message = list( @@ -107,7 +113,22 @@ app_server <- function(input, output, session) { tags$p( class = "color-section-total", paste0(nrow(rv$site$all_colors), " unique colors in total") - ) + ), + if (!is.null(rv$site$top_colors$white_black) && nrow(rv$site$top_colors$white_black) > 0) { + wb <- rv$site$top_colors$white_black + div( + class = "color-section-neutrals", + tags$span(class = "color-section-neutrals-label", "Neutrals"), + lapply(seq_len(nrow(wb)), function(j) { + hex_wb <- wb$Color[j] + div( + class = "color-neutral-chip", + div(class = "color-neutral-swatch", style = paste0("background:", hex_wb, ";")), + tags$span(class = "color-neutral-hex", hex_wb) + ) + }) + ) + } ), # Right: backing gradient + fan cards div( @@ -191,7 +212,12 @@ app_server <- function(input, output, session) { tags$ol( class = "result-link-list", lapply(rv$site$fonts, function(font) { - tags$li(font[1]) + family_raw <- font[["family"]] + font_name <- strsplit(as.character(family_raw), ":")[[1]][1] + font_url <- paste0("https://fonts.google.com/specimen/", gsub(" ", "+", font_name)) + tags$li( + tags$a(href = font_url, target = "_blank", font_name) + ) }) ) ) @@ -226,7 +252,7 @@ app_server <- function(input, output, session) { # 5. All colors section div( - class = "result-section result-section-last", + class = "result-section", div( class = "result-section-label", tags$span(class = "result-section-tag", "All colors"), @@ -235,8 +261,107 @@ app_server <- function(input, output, session) { ), div( class = "result-section-card p-0", + div( + class = "all-colors-toolbar", + tags$span(class = "all-colors-sort-label", "Sort"), + radioButtons( + inputId = "color_sort", + label = NULL, + choices = c("Frequency" = "freq", "Hue" = "hue"), + selected = "freq", + inline = TRUE + ) + ), uiOutput("all_colors") ) + ), + + # 6. Live preview section + div( + class = "result-section result-section-last", + div( + class = "result-section-label", + tags$span(class = "result-section-tag", "Preview"), + tags$h3(class = "result-section-title", "Live mockup"), + tags$p( + class = "result-section-desc", + HTML("Bootstrap components rendered with the extracted theme.
What your app could look like.") + ) + ), + div( + class = "result-section-card p-0 overflow-hidden", + uiOutput("preview_controls"), + div( + class = "theme-preview-wrap", + # Navbar + tags$nav( + class = "navbar bg-primary px-3 py-2", + `data-bs-theme` = "dark", + div( + class = "container-fluid p-0", + tags$a(class = "navbar-brand text-white fw-semibold me-4", rv$site$domain), + div( + class = "navbar-nav flex-row gap-3", + tags$a(class = "nav-link active text-white", href = "#", "Home"), + tags$a(class = "nav-link text-white opacity-75", href = "#", "Plots"), + tags$a(class = "nav-link text-white opacity-75", href = "#", "Data") + ) + ) + ), + # Body + div( + class = "theme-preview-body container-fluid py-3 px-3", + div( + class = "row g-3", + # Card 1 + div( + class = "col-md-6", + div( + class = "card", + div(class = "card-header fw-semibold", "Dashboard"), + div( + class = "card-body", + tags$p(class = "card-text text-muted small mb-3", + "Your Shiny app, styled with the extracted palette."), + div( + class = "d-flex flex-wrap gap-2", + tags$button(class = "btn btn-primary btn-sm", "Primary"), + tags$button(class = "btn btn-secondary btn-sm", "Secondary"), + tags$button(class = "btn btn-outline-primary btn-sm", "Outline") + ) + ) + ) + ), + # Card 2 + div( + class = "col-md-6", + div( + class = "card", + div(class = "card-header fw-semibold", "Controls"), + div( + class = "card-body", + div( + class = "mb-3", + tags$label(class = "form-label small text-muted", "Select a variable"), + tags$select( + class = "form-select form-select-sm", + tags$option("Option A"), + tags$option("Option B"), + tags$option("Option C") + ) + ), + div( + class = "input-group input-group-sm", + tags$input(type = "number", class = "form-control", value = "42"), + tags$button(class = "btn btn-primary", "Run") + ) + ) + ) + ) + ) + ) + ) + ) ) ) }) @@ -247,9 +372,86 @@ app_server <- function(input, output, session) { rv$result }) + output$preview_controls <- renderUI({ + req(rv$site, rv$preview_primary, rv$preview_secondary) + colors <- rv$site$top_colors$top_colors$Color + + make_chips <- function(role, current) { + div( + class = "theme-preview-editor-row", + tags$span(class = "preview-role-label", role), + div( + class = "preview-palette", + lapply(colors, function(hex) { + div( + class = paste0("preview-chip", if (hex == current) " preview-chip-active" else ""), + style = paste0("background:", hex, ";"), + onclick = paste0( + 'Shiny.setInputValue("preview_', + tolower(role), + '", "', hex, '", {priority: "event"})' + ) + ) + }) + ) + ) + } + + div( + class = "theme-preview-editor", + make_chips("Primary", rv$preview_primary), + make_chips("Secondary", rv$preview_secondary) + ) + }) + + observeEvent(input$preview_primary, { + req(rv$site) + rv$preview_primary <- input$preview_primary + session$setCurrentTheme( + bslib::bs_theme_update( + rv$site$shiny_theme, + primary = rv$preview_primary, + secondary = rv$preview_secondary + ) + ) + }) + + observeEvent(input$preview_secondary, { + req(rv$site) + rv$preview_secondary <- input$preview_secondary + session$setCurrentTheme( + bslib::bs_theme_update( + rv$site$shiny_theme, + primary = rv$preview_primary, + secondary = rv$preview_secondary + ) + ) + }) + output$all_colors <- renderUI({ req(rv$site$all_colors) - df <- rv$site$all_colors + df <- rv$site$all_colors + + if (!is.null(input$color_sort) && input$color_sort == "hue") { + hex_to_hue <- function(hex) { + hex <- sub("^#", "", hex) + r <- strtoi(substr(hex, 1, 2), 16L) / 255 + g <- strtoi(substr(hex, 3, 4), 16L) / 255 + b <- strtoi(substr(hex, 5, 6), 16L) / 255 + max_c <- max(r, g, b) + min_c <- min(r, g, b) + if (max_c == min_c) return(0) + delta <- max_c - min_c + if (max_c == r) h <- (g - b) / delta + else if (max_c == g) h <- 2 + (b - r) / delta + else h <- 4 + (r - g) / delta + h <- h * 60 + if (h < 0) h <- h + 360 + h + } + df <- df[order(sapply(df$Color, hex_to_hue)), ] + } + max_count <- max(df$Count) div( @@ -269,6 +471,11 @@ app_server <- function(input, output, session) { div(class = "color-swatch-bar", style = paste0("width:", bar, "%;")) ), tags$span(class = "color-swatch-count", paste0(cnt, "\u00d7")) + ), + tags$button( + class = "swatch-copy-btn", + onclick = paste0('copySwatchHex("', hex, '", this)'), + "Copy" ) ) }) diff --git a/R/css2r.R b/R/css2r.R index cd98a27..aa6762b 100644 --- a/R/css2r.R +++ b/R/css2r.R @@ -255,6 +255,19 @@ css2r <- R6Class( ) ) + # Also scan downloaded CSS for @import Google Fonts URLs + if (!is.null(self$css_content)) { + css_import_matches <- regmatches( + self$css_content, + gregexpr( + pattern = "https://fonts\\.googleapis\\.com[^\"')\\s]+", + text = self$css_content, + perl = TRUE + ) + )[[1]] + google_font_links <- unique(c(google_font_links, css_import_matches)) + } + if (length(google_font_links) > 0) { google_fonts_params <- private$extract_google_font_params( links = google_font_links diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 0000000..a1dc34e --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,32 @@ +# ROADMAP + +## Limitations moteur (`R/css2r.R`) + +- Regex hex 6 chiffres uniquement → manque `rgb()`, `hsl()`, `#ABC`, variables CSS (`--my-color`) +- Top 4 couleurs hardcodé (line 229 : `head(other_colors, 4)`) → rendre configurable +- CSS même domaine uniquement → CDN externes exclus (Bootstrap, Tailwind…) +- Google Fonts uniquement (`fonts.googleapis.com`) → pas de `@font-face`, Adobe Fonts +- Balises `` uniquement → pas de `