55# ' @import shiny
66# ' @noRd
77app_server <- function (input , output , session ) {
8- rv <- reactiveValues(site = NULL )
8+ rv <- reactiveValues(site = NULL , preview_primary = NULL , preview_secondary = NULL )
99
1010 observeEvent(input $ analyze_btn , {
1111 rv $ result <- NULL
@@ -66,6 +66,12 @@ app_server <- function(input, output, session) {
6666 observeEvent(rv $ continue , {
6767 req(rv $ site )
6868
69+ session $ setCurrentTheme(rv $ site $ shiny_theme )
70+
71+ top_colors_df_init <- rv $ site $ top_colors $ top_colors
72+ rv $ preview_primary <- top_colors_df_init $ Color [1 ]
73+ rv $ preview_secondary <- if (nrow(top_colors_df_init ) > 1 ) top_colors_df_init $ Color [2 ] else top_colors_df_init $ Color [1 ]
74+
6975 session $ sendCustomMessage(
7076 type = " apply_gradient" ,
7177 message = list (
@@ -107,7 +113,22 @@ app_server <- function(input, output, session) {
107113 tags $ p(
108114 class = " color-section-total" ,
109115 paste0(nrow(rv $ site $ all_colors ), " unique colors in total" )
110- )
116+ ),
117+ if (! is.null(rv $ site $ top_colors $ white_black ) && nrow(rv $ site $ top_colors $ white_black ) > 0 ) {
118+ wb <- rv $ site $ top_colors $ white_black
119+ div(
120+ class = " color-section-neutrals" ,
121+ tags $ span(class = " color-section-neutrals-label" , " Neutrals" ),
122+ lapply(seq_len(nrow(wb )), function (j ) {
123+ hex_wb <- wb $ Color [j ]
124+ div(
125+ class = " color-neutral-chip" ,
126+ div(class = " color-neutral-swatch" , style = paste0(" background:" , hex_wb , " ;" )),
127+ tags $ span(class = " color-neutral-hex" , hex_wb )
128+ )
129+ })
130+ )
131+ }
111132 ),
112133 # Right: backing gradient + fan cards
113134 div(
@@ -191,7 +212,12 @@ app_server <- function(input, output, session) {
191212 tags $ ol(
192213 class = " result-link-list" ,
193214 lapply(rv $ site $ fonts , function (font ) {
194- tags $ li(font [1 ])
215+ family_raw <- font [[" family" ]]
216+ font_name <- strsplit(as.character(family_raw ), " :" )[[1 ]][1 ]
217+ font_url <- paste0(" https://fonts.google.com/specimen/" , gsub(" " , " +" , font_name ))
218+ tags $ li(
219+ tags $ a(href = font_url , target = " _blank" , font_name )
220+ )
195221 })
196222 )
197223 )
@@ -226,7 +252,7 @@ app_server <- function(input, output, session) {
226252
227253 # 5. All colors section
228254 div(
229- class = " result-section result-section-last " ,
255+ class = " result-section" ,
230256 div(
231257 class = " result-section-label" ,
232258 tags $ span(class = " result-section-tag" , " All colors" ),
@@ -235,8 +261,107 @@ app_server <- function(input, output, session) {
235261 ),
236262 div(
237263 class = " result-section-card p-0" ,
264+ div(
265+ class = " all-colors-toolbar" ,
266+ tags $ span(class = " all-colors-sort-label" , " Sort" ),
267+ radioButtons(
268+ inputId = " color_sort" ,
269+ label = NULL ,
270+ choices = c(" Frequency" = " freq" , " Hue" = " hue" ),
271+ selected = " freq" ,
272+ inline = TRUE
273+ )
274+ ),
238275 uiOutput(" all_colors" )
239276 )
277+ ),
278+
279+ # 6. Live preview section
280+ div(
281+ class = " result-section result-section-last" ,
282+ div(
283+ class = " result-section-label" ,
284+ tags $ span(class = " result-section-tag" , " Preview" ),
285+ tags $ h3(class = " result-section-title" , " Live mockup" ),
286+ tags $ p(
287+ class = " result-section-desc" ,
288+ HTML(" Bootstrap components rendered with the extracted theme.<br>What your app could look like." )
289+ )
290+ ),
291+ div(
292+ class = " result-section-card p-0 overflow-hidden" ,
293+ uiOutput(" preview_controls" ),
294+ div(
295+ class = " theme-preview-wrap" ,
296+ # Navbar
297+ tags $ nav(
298+ class = " navbar bg-primary px-3 py-2" ,
299+ `data-bs-theme` = " dark" ,
300+ div(
301+ class = " container-fluid p-0" ,
302+ tags $ a(class = " navbar-brand text-white fw-semibold me-4" , rv $ site $ domain ),
303+ div(
304+ class = " navbar-nav flex-row gap-3" ,
305+ tags $ a(class = " nav-link active text-white" , href = " #" , " Home" ),
306+ tags $ a(class = " nav-link text-white opacity-75" , href = " #" , " Plots" ),
307+ tags $ a(class = " nav-link text-white opacity-75" , href = " #" , " Data" )
308+ )
309+ )
310+ ),
311+ # Body
312+ div(
313+ class = " theme-preview-body container-fluid py-3 px-3" ,
314+ div(
315+ class = " row g-3" ,
316+ # Card 1
317+ div(
318+ class = " col-md-6" ,
319+ div(
320+ class = " card" ,
321+ div(class = " card-header fw-semibold" , " Dashboard" ),
322+ div(
323+ class = " card-body" ,
324+ tags $ p(class = " card-text text-muted small mb-3" ,
325+ " Your Shiny app, styled with the extracted palette." ),
326+ div(
327+ class = " d-flex flex-wrap gap-2" ,
328+ tags $ button(class = " btn btn-primary btn-sm" , " Primary" ),
329+ tags $ button(class = " btn btn-secondary btn-sm" , " Secondary" ),
330+ tags $ button(class = " btn btn-outline-primary btn-sm" , " Outline" )
331+ )
332+ )
333+ )
334+ ),
335+ # Card 2
336+ div(
337+ class = " col-md-6" ,
338+ div(
339+ class = " card" ,
340+ div(class = " card-header fw-semibold" , " Controls" ),
341+ div(
342+ class = " card-body" ,
343+ div(
344+ class = " mb-3" ,
345+ tags $ label(class = " form-label small text-muted" , " Select a variable" ),
346+ tags $ select(
347+ class = " form-select form-select-sm" ,
348+ tags $ option(" Option A" ),
349+ tags $ option(" Option B" ),
350+ tags $ option(" Option C" )
351+ )
352+ ),
353+ div(
354+ class = " input-group input-group-sm" ,
355+ tags $ input(type = " number" , class = " form-control" , value = " 42" ),
356+ tags $ button(class = " btn btn-primary" , " Run" )
357+ )
358+ )
359+ )
360+ )
361+ )
362+ )
363+ )
364+ )
240365 )
241366 )
242367 })
@@ -247,9 +372,86 @@ app_server <- function(input, output, session) {
247372 rv $ result
248373 })
249374
375+ output $ preview_controls <- renderUI({
376+ req(rv $ site , rv $ preview_primary , rv $ preview_secondary )
377+ colors <- rv $ site $ top_colors $ top_colors $ Color
378+
379+ make_chips <- function (role , current ) {
380+ div(
381+ class = " theme-preview-editor-row" ,
382+ tags $ span(class = " preview-role-label" , role ),
383+ div(
384+ class = " preview-palette" ,
385+ lapply(colors , function (hex ) {
386+ div(
387+ class = paste0(" preview-chip" , if (hex == current ) " preview-chip-active" else " " ),
388+ style = paste0(" background:" , hex , " ;" ),
389+ onclick = paste0(
390+ ' Shiny.setInputValue("preview_' ,
391+ tolower(role ),
392+ ' ", "' , hex , ' ", {priority: "event"})'
393+ )
394+ )
395+ })
396+ )
397+ )
398+ }
399+
400+ div(
401+ class = " theme-preview-editor" ,
402+ make_chips(" Primary" , rv $ preview_primary ),
403+ make_chips(" Secondary" , rv $ preview_secondary )
404+ )
405+ })
406+
407+ observeEvent(input $ preview_primary , {
408+ req(rv $ site )
409+ rv $ preview_primary <- input $ preview_primary
410+ session $ setCurrentTheme(
411+ bslib :: bs_theme_update(
412+ rv $ site $ shiny_theme ,
413+ primary = rv $ preview_primary ,
414+ secondary = rv $ preview_secondary
415+ )
416+ )
417+ })
418+
419+ observeEvent(input $ preview_secondary , {
420+ req(rv $ site )
421+ rv $ preview_secondary <- input $ preview_secondary
422+ session $ setCurrentTheme(
423+ bslib :: bs_theme_update(
424+ rv $ site $ shiny_theme ,
425+ primary = rv $ preview_primary ,
426+ secondary = rv $ preview_secondary
427+ )
428+ )
429+ })
430+
250431 output $ all_colors <- renderUI({
251432 req(rv $ site $ all_colors )
252- df <- rv $ site $ all_colors
433+ df <- rv $ site $ all_colors
434+
435+ if (! is.null(input $ color_sort ) && input $ color_sort == " hue" ) {
436+ hex_to_hue <- function (hex ) {
437+ hex <- sub(" ^#" , " " , hex )
438+ r <- strtoi(substr(hex , 1 , 2 ), 16L ) / 255
439+ g <- strtoi(substr(hex , 3 , 4 ), 16L ) / 255
440+ b <- strtoi(substr(hex , 5 , 6 ), 16L ) / 255
441+ max_c <- max(r , g , b )
442+ min_c <- min(r , g , b )
443+ if (max_c == min_c ) return (0 )
444+ delta <- max_c - min_c
445+ if (max_c == r ) h <- (g - b ) / delta
446+ else if (max_c == g ) h <- 2 + (b - r ) / delta
447+ else h <- 4 + (r - g ) / delta
448+ h <- h * 60
449+ if (h < 0 ) h <- h + 360
450+ h
451+ }
452+ df <- df [order(sapply(df $ Color , hex_to_hue )), ]
453+ }
454+
253455 max_count <- max(df $ Count )
254456
255457 div(
@@ -269,6 +471,11 @@ app_server <- function(input, output, session) {
269471 div(class = " color-swatch-bar" , style = paste0(" width:" , bar , " %;" ))
270472 ),
271473 tags $ span(class = " color-swatch-count" , paste0(cnt , " \u 00d7" ))
474+ ),
475+ tags $ button(
476+ class = " swatch-copy-btn" ,
477+ onclick = paste0(' copySwatchHex("' , hex , ' ", this)' ),
478+ " Copy"
272479 )
273480 )
274481 })
0 commit comments