30
30
# ' \section{WebR}{
31
31
# ' \ifelse{html}{
32
32
# ' \out{
33
- # ' <a href="webR_URL">\U0001F310 View in webR REPL</a >
33
+ # ' <div class="rocleteer-webr-container">...</div >
34
34
# ' }
35
35
# ' }{
36
36
# ' \ifelse{latex}{
56
56
# ' - **LaTeX/PDF**: Plain URL link to webR session
57
57
# ' - **Other formats**: Informational message about limited support
58
58
# '
59
+ # ' @section Styling and Theming:
60
+ # '
61
+ # ' The webR Section HTML output uses Bootstrap-compatible CSS classes and custom
62
+ # ' properties to link themeing to pkgdown's existing CSS themes. The styles
63
+ # ' follow the CSS custom properties pattern:
64
+ # '
65
+ # ' - Private variables (`--_*`): Used internally
66
+ # ' - Public variables (`--rocleteer-webr-*`): For user customization
67
+ # ' - Bootstrap variables (`--bs-*`): Used as fallbacks
68
+ # '
69
+ # ' Main CSS classes are:
70
+ # '
71
+ # ' - `.rocleteer-webr-container`: Main container
72
+ # ' - `.rocleteer-webr-warning`: Experimental feature warning
73
+ # ' - `.rocleteer-webr-btn`, `.rocleteer-webr-btn-primary`, etc.: Button styling
74
+ # '
75
+ # ' To customize appearance in pkgdown sites, override CSS custom properties by
76
+ # ' including into a file:
77
+ # '
78
+ # ' ```css
79
+ # ' .rocleteer-webr-container {
80
+ # ' --rocleteer-webr-container-bg: #f0f8ff;
81
+ # ' --rocleteer-webr-btn-primary-bg: #1976d2;
82
+ # ' }
83
+ # ' ```
84
+ # '
59
85
# ' @section Parameters:
60
86
# '
61
87
# ' The tag supports optional parameters (these override global `DESCRIPTION` config):
@@ -326,7 +352,7 @@ encode_webr_code <- function(code, filename = "example.R", autorun = FALSE) {
326
352
if (! requireNamespace(" base64enc" , quietly = TRUE )) {
327
353
stop(" Package 'base64enc' is required for @examplesWebR tag" )
328
354
}
329
-
355
+
330
356
# Create the share item structure exactly as webR expects
331
357
share_item <- list (
332
358
list (
@@ -519,6 +545,167 @@ parse_webr_params <- function(tag_line, global_config = list()) {
519
545
return (params )
520
546
}
521
547
548
+ # ' Generate Rocleteer webR CSS styles
549
+ # '
550
+ # ' Create the complete CSS for webR components as an inline style block
551
+ # '
552
+ # ' @return
553
+ # ' HTML string with complete CSS styles
554
+ # '
555
+ # ' @noRd
556
+ webr_inline_css <- function () {
557
+ paste0(
558
+ ' <style id="rocleteer-webr-styles">' ,
559
+ ' /* WebR Interactive Examples CSS - rocleteer package */' ,
560
+ ' .rocleteer-webr-container {' ,
561
+ ' --_container-bg: var(--rocleteer-webr-container-bg, var(--bs-light, #f8f9fa));' ,
562
+ ' --_container-border: var(--rocleteer-webr-container-border, var(--bs-border-color, #dee2e6));' ,
563
+ ' --_container-border-radius: var(--rocleteer-webr-container-border-radius, var(--bs-border-radius, 8px));' ,
564
+ ' --_warning-bg: var(--rocleteer-webr-warning-bg, var(--bs-info-bg-subtle, #e7f3ff));' ,
565
+ ' --_warning-border: var(--rocleteer-webr-warning-border, var(--bs-info-border-subtle, #b3d9ff));' ,
566
+ ' --_warning-text: var(--rocleteer-webr-warning-text, var(--bs-info-text-emphasis, #0c5aa6));' ,
567
+ ' --_btn-primary-bg: var(--rocleteer-webr-btn-primary-bg, var(--bs-primary, #007bff));' ,
568
+ ' --_btn-primary-hover: var(--rocleteer-webr-btn-primary-hover, var(--bs-primary-hover, #0056b3));' ,
569
+ ' --_btn-success-bg: var(--rocleteer-webr-btn-success-bg, var(--bs-success, #28a745));' ,
570
+ ' --_btn-success-hover: var(--rocleteer-webr-btn-success-hover, var(--bs-success-hover, #1e7e34));' ,
571
+ ' --_btn-secondary-bg: var(--rocleteer-webr-btn-secondary-bg, var(--bs-secondary, #6c757d));' ,
572
+ ' --_btn-secondary-hover: var(--rocleteer-webr-btn-secondary-hover, var(--bs-secondary-hover, #545b62));' ,
573
+ ' --_text-color: var(--rocleteer-webr-text-color, var(--bs-body-color, #333));' ,
574
+ ' --_text-emphasis: var(--rocleteer-webr-text-emphasis, var(--bs-emphasis-color, #000));' ,
575
+ ' --_container-padding: var(--rocleteer-webr-container-padding, 16px);' ,
576
+ ' --_container-margin: var(--rocleteer-webr-container-margin, 16px 0);' ,
577
+ ' --_btn-padding: var(--rocleteer-webr-btn-padding, 12px 20px);' ,
578
+ ' --_btn-margin: var(--rocleteer-webr-btn-margin, 0 8px 0 0);' ,
579
+ ' --_font-size: var(--rocleteer-webr-font-size, var(--bs-body-font-size, 14px));' ,
580
+ ' --_font-weight-bold: var(--rocleteer-webr-font-weight-bold, var(--bs-font-weight-bold, 700));' ,
581
+ ' border: 1px solid var(--_container-border);' ,
582
+ ' border-radius: var(--_container-border-radius);' ,
583
+ ' padding: var(--_container-padding);' ,
584
+ ' margin: var(--_container-margin);' ,
585
+ ' background-color: var(--_container-bg);' ,
586
+ ' color: var(--_text-color);' ,
587
+ ' font-size: var(--_font-size);' ,
588
+ ' }' ,
589
+ ' .rocleteer-webr-warning {' ,
590
+ ' background-color: var(--_warning-bg);' ,
591
+ ' border: 1px solid var(--_warning-border);' ,
592
+ ' border-radius: var(--bs-border-radius, 4px);' ,
593
+ ' padding: 12px;' ,
594
+ ' margin-bottom: 16px;' ,
595
+ ' color: var(--_warning-text);' ,
596
+ ' font-size: var(--_font-size);' ,
597
+ ' }' ,
598
+ ' .rocleteer-webr-warning strong {' ,
599
+ ' font-weight: var(--_font-weight-bold);' ,
600
+ ' }' ,
601
+ ' .rocleteer-webr-btn {' ,
602
+ ' display: inline-block;' ,
603
+ ' padding: var(--_btn-padding);' ,
604
+ ' margin: var(--_btn-margin);' ,
605
+ ' border: none;' ,
606
+ ' border-radius: var(--bs-border-radius, 4px);' ,
607
+ ' cursor: pointer;' ,
608
+ ' text-decoration: none;' ,
609
+ ' font-size: var(--_font-size);' ,
610
+ ' color: white;' ,
611
+ ' transition: background-color 0.15s ease-in-out;' ,
612
+ ' }' ,
613
+ ' .rocleteer-webr-btn-primary {' ,
614
+ ' background-color: var(--_btn-primary-bg);' ,
615
+ ' }' ,
616
+ ' .rocleteer-webr-btn-primary:hover {' ,
617
+ ' background-color: var(--_btn-primary-hover);' ,
618
+ ' color: white;' ,
619
+ ' text-decoration: none;' ,
620
+ ' }' ,
621
+ ' .rocleteer-webr-btn-success {' ,
622
+ ' background-color: var(--_btn-success-bg);' ,
623
+ ' }' ,
624
+ ' .rocleteer-webr-btn-success:hover {' ,
625
+ ' background-color: var(--_btn-success-hover);' ,
626
+ ' }' ,
627
+ ' .rocleteer-webr-btn-secondary {' ,
628
+ ' background-color: var(--_btn-secondary-bg);' ,
629
+ ' }' ,
630
+ ' .rocleteer-webr-btn-secondary:hover {' ,
631
+ ' background-color: var(--_btn-secondary-hover);' ,
632
+ ' }' ,
633
+ ' .rocleteer-webr-initial-text {' ,
634
+ ' margin: 8px 0;' ,
635
+ ' font-weight: var(--_font-weight-bold);' ,
636
+ ' color: var(--_text-emphasis);' ,
637
+ ' }' ,
638
+ ' .rocleteer-webr-iframe-container {' ,
639
+ ' display: none;' ,
640
+ ' }' ,
641
+ ' .rocleteer-webr-container.rocleteer-webr-fullwidth {' ,
642
+ ' position: fixed;' ,
643
+ ' top: 0;' ,
644
+ ' left: 0;' ,
645
+ ' width: 100vw;' ,
646
+ ' height: 100vh;' ,
647
+ ' z-index: var(--rocleteer-webr-fullwidth-z-index, 1050);' ,
648
+ ' margin: 0;' ,
649
+ ' border-radius: 0;' ,
650
+ ' border: none;' ,
651
+ ' overflow: auto;' ,
652
+ ' background-color: var(--_container-bg);' ,
653
+ ' transition: all 0.3s ease-in-out;' ,
654
+ ' }' ,
655
+ ' .rocleteer-webr-container.rocleteer-webr-fullwidth .rocleteer-webr-iframe {' ,
656
+ ' height: calc(100vh - 180px) !important;' ,
657
+ ' border: none;' ,
658
+ ' }' ,
659
+ ' .rocleteer-webr-fullwidth-backdrop {' ,
660
+ ' position: fixed;' ,
661
+ ' top: 0;' ,
662
+ ' left: 0;' ,
663
+ ' width: 100vw;' ,
664
+ ' height: 100vh;' ,
665
+ ' background-color: rgba(0, 0, 0, 0.5);' ,
666
+ ' z-index: var(--rocleteer-webr-backdrop-z-index, 1040);' ,
667
+ ' display: none;' ,
668
+ ' }' ,
669
+ ' .rocleteer-webr-iframe-controls {' ,
670
+ ' margin-bottom: 12px;' ,
671
+ ' }' ,
672
+ ' .rocleteer-webr-iframe-controls .rocleteer-webr-btn {' ,
673
+ ' padding: 8px 16px;' ,
674
+ ' font-size: var(--_font-size);' ,
675
+ ' }' ,
676
+ ' .rocleteer-webr-iframe {' ,
677
+ ' width: 100\\ %;' ,
678
+ ' border: 1px solid var(--_container-border);' ,
679
+ ' border-radius: var(--bs-border-radius, 4px);' ,
680
+ ' }' ,
681
+ ' @media (max-width: 576px) {' ,
682
+ ' .rocleteer-webr-container {' ,
683
+ ' --_container-padding: 12px;' ,
684
+ ' --_btn-padding: 10px 16px;' ,
685
+ ' --_font-size: 13px;' ,
686
+ ' }' ,
687
+ ' .rocleteer-webr-btn {' ,
688
+ ' display: block;' ,
689
+ ' margin: 4px 0;' ,
690
+ ' text-align: center;' ,
691
+ ' }' ,
692
+ ' .rocleteer-webr-iframe-controls .rocleteer-webr-btn {' ,
693
+ ' display: inline-block;' ,
694
+ ' margin: 0 4px 0 0;' ,
695
+ ' font-size: 12px;' ,
696
+ ' padding: 6px 12px;' ,
697
+ ' }' ,
698
+ ' .rocleteer-webr-container.rocleteer-webr-fullwidth {' ,
699
+ ' padding: 8px;' ,
700
+ ' }' ,
701
+ ' .rocleteer-webr-container.rocleteer-webr-fullwidth .rocleteer-webr-iframe {' ,
702
+ ' height: calc(100vh - 120px) !important;' ,
703
+ ' }' ,
704
+ ' }' ,
705
+ ' </style>'
706
+ )
707
+ }
708
+
522
709
# ' Generate webR warning HTML
523
710
# '
524
711
# ' Create standardized warning message for webR examples
@@ -529,7 +716,7 @@ parse_webr_params <- function(tag_line, global_config = list()) {
529
716
# ' @noRd
530
717
webr_experimental_warning <- function () {
531
718
paste0(
532
- ' <div class="webr-warning" style="background-color: #e7f3ff; border: 1px solid #b3d9ff; border-radius: 4px; padding: 12px; margin-bottom: 16px; font-size: 14px; color: #0c5aa6; ">' ,
719
+ ' <div class="rocleteer- webr-warning">' ,
533
720
' <strong>\U 0001F9EA Experimental:</strong> Interactive webR examples are a new feature. ' ,
534
721
' Loading may take a moment, and the package version might differ from this documentation.' ,
535
722
' </div>'
@@ -590,15 +777,17 @@ webr_repl_href <- function(encoded_code, version = "latest", mode = "", channel
590
777
webr_repl_link <- function (encoded_code , version = " latest" , mode = " " , channel = " " ) {
591
778
url <- webr_repl_href(encoded_code , version , mode , channel )
592
779
html <- paste0(
593
- ' <div class="webr-container" style="border: 1px solid #ddd; border-radius: 8px; padding: 16px; margin: 16px 0; background-color: #f8f9fa;">' ,
780
+ # Include CSS
781
+ # TODO: Figure out how to include it once? Post parser re-write?
782
+ webr_inline_css(),
783
+
784
+ ' <div class="rocleteer-webr-container">' ,
594
785
595
786
# Warning message
596
787
webr_experimental_warning(),
597
788
598
789
# Link button
599
- ' <p><a href="' , url , ' " target="_blank" ' ,
600
- ' style="background-color: #007bff; color: white; padding: 12px 20px; ' ,
601
- ' text-decoration: none; border-radius: 4px; font-size: 14px; display: inline-block;">' ,
790
+ ' <p><a href="' , url , ' " target="_blank" class="rocleteer-webr-btn rocleteer-webr-btn-primary">' ,
602
791
' \U 0001F310 View in webR REPL</a></p>' ,
603
792
604
793
' </div>'
@@ -622,61 +811,105 @@ webr_repl_link <- function(encoded_code, version = "latest", mode = "", channel
622
811
# ' @noRd
623
812
webr_repl_iframe <- function (encoded_code , version = " latest" , height = 300 , mode = " " , channel = " " ) {
624
813
url <- webr_repl_href(encoded_code , version , mode , channel )
625
-
814
+
626
815
# Create an ID for this iframe (based on the encoded code)
627
816
hashed_id <- abs(sum(utf8ToInt(substr(encoded_code , 1 , 10 ))))
628
817
iframe_id <- paste0(" webr_iframe_" , hashed_id )
629
818
630
819
html <- paste0(
631
- ' <div class="webr-container" style="border: 1px solid #ddd; border-radius: 8px; padding: 16px; margin: 16px 0; background-color: #f8f9fa;">' ,
820
+ # Include CSS once
821
+ webr_inline_css(),
822
+
823
+ ' <div class="rocleteer-webr-container">' ,
632
824
633
825
# Warning message
634
826
webr_experimental_warning(),
635
827
636
828
# Initial buttons (before iframe loads)
637
- ' <div id="' , iframe_id , ' _initial" class="webr-initial">' ,
638
- ' <p style="margin: 8px 0; font-weight: bold; color: #333;">Interactive Example Available</p>' ,
639
- ' <button onclick="loadWebRIframe(\' ' , iframe_id , ' \' , \' ' , url , ' \' , ' , height , ' )" ' ,
640
- ' style="background-color: #28a745; color: white; padding: 12px 20px; border: none; border-radius: 4px; ' ,
641
- ' cursor: pointer; font-size: 14px; margin-right: 8px;">' ,
829
+ ' <div id="' , iframe_id , ' _initial" class="rocleteer-webr-initial">' ,
830
+ ' <p class="rocleteer-webr-initial-text">Interactive Example Available</p>' ,
831
+ ' <button onclick="loadWebRIframe(\' ' , iframe_id , ' \' , \' ' , url , ' \' , ' , height , ' , false)" ' ,
832
+ ' class="rocleteer-webr-btn rocleteer-webr-btn-success rocleteer-webr-btn-try">' ,
642
833
' \U 0001F680 Try it in your browser</button>' ,
643
834
' <button onclick="window.open(\' ' , url , ' \' , \' _blank\' )" ' ,
644
- ' style="background-color: #007bff; color: white; padding: 12px 20px; border: none; border-radius: 4px; ' ,
645
- ' cursor: pointer; font-size: 14px;">' ,
835
+ ' class="rocleteer-webr-btn rocleteer-webr-btn-primary rocleteer-webr-btn-open">' ,
646
836
' \U 0001F310 Open in Tab</button>' ,
647
837
' </div>' ,
648
838
649
839
# Iframe container (hidden initially)
650
- ' <div id="' , iframe_id , ' _container" class="webr-iframe-container" style="display: none; ">' ,
651
- ' <div style="margin-bottom: 12px; ">' ,
840
+ ' <div id="' , iframe_id , ' _container" class="rocleteer- webr-iframe-container">' ,
841
+ ' <div class="rocleteer-webr-iframe-controls ">' ,
652
842
' <button onclick="hideWebRIframe(\' ' , iframe_id , ' \' )" ' ,
653
- ' style="background-color: #6c757d; color: white; padding: 8px 16px; border: none; border-radius: 4px; ' ,
654
- ' cursor: pointer; font-size: 14px; margin-right: 8px;">' ,
843
+ ' class="rocleteer-webr-btn rocleteer-webr-btn-secondary rocleteer-webr-btn-back">' ,
655
844
' \U 0001F519 Go back</button>' ,
845
+ ' <button onclick="toggleWebRFullWidth(\' ' , iframe_id , ' \' )" ' ,
846
+ ' class="rocleteer-webr-btn rocleteer-webr-btn-primary rocleteer-webr-btn-toggle" ' ,
847
+ ' id="' , iframe_id , ' _toggle">' ,
848
+ ' \U 00026F6 Full Screen</button>' ,
656
849
' <button onclick="window.open(\' ' , url , ' \' , \' _blank\' )" ' ,
657
- ' style="background-color: #007bff; color: white; padding: 8px 16px; border: none; border-radius: 4px; ' ,
658
- ' cursor: pointer; font-size: 14px;">' ,
850
+ ' class="rocleteer-webr-btn rocleteer-webr-btn-primary rocleteer-webr-btn-open">' ,
659
851
' \U 0001F310 Open in Tab</button>' ,
660
852
' </div>' ,
661
- ' <div id="' , iframe_id , ' _content"></div>' ,
853
+ ' <div id="' , iframe_id , ' _content" class="rocleteer-webr-iframe-content" ></div>' ,
662
854
' </div>' ,
663
855
664
856
' </div>' ,
665
857
666
858
# JavaScript for iframe management
667
859
' <script>' ,
668
- ' function loadWebRIframe(id, url, height) {' ,
860
+ ' function loadWebRIframe(id, url, height, fullwidth) {' ,
861
+ ' fullwidth = fullwidth || false;' ,
669
862
' document.getElementById(id + "_initial").style.display = "none";' ,
670
863
' document.getElementById(id + "_container").style.display = "block";' ,
671
864
' document.getElementById(id + "_content").innerHTML = ' ,
672
- ' \' <iframe src="\' + url + \' " width="100\\ %" height="\' + height + \' px" \' +' ,
673
- ' \' style="border: 1px solid #ddd; border-radius: 4px;" title="webR REPL"></iframe>\' ;' ,
865
+ ' \' <iframe src="\' + url + \' " class="rocleteer-webr-iframe" height="\' + height + \' px" \' +' ,
866
+ ' \' title="webR REPL"></iframe>\' ;' ,
867
+ ' if (fullwidth) {' ,
868
+ ' enterWebRFullWidth(id);' ,
869
+ ' }' ,
674
870
' }' ,
675
871
' function hideWebRIframe(id) {' ,
872
+ ' exitWebRFullWidth(id);' ,
676
873
' document.getElementById(id + "_initial").style.display = "block";' ,
677
874
' document.getElementById(id + "_container").style.display = "none";' ,
678
875
' document.getElementById(id + "_content").innerHTML = "";' ,
679
876
' }' ,
877
+ ' function toggleWebRFullWidth(id) {' ,
878
+ ' var container = document.getElementById(id + "_container").closest(".rocleteer-webr-container");' ,
879
+ ' if (container.classList.contains("rocleteer-webr-fullwidth")) {' ,
880
+ ' exitWebRFullWidth(id);' ,
881
+ ' } else {' ,
882
+ ' enterWebRFullWidth(id);' ,
883
+ ' }' ,
884
+ ' }' ,
885
+ ' function enterWebRFullWidth(id) {' ,
886
+ ' var container = document.getElementById(id + "_container").closest(".rocleteer-webr-container");' ,
887
+ ' var backdrop = document.createElement("div");' ,
888
+ ' backdrop.className = "rocleteer-webr-fullwidth-backdrop";' ,
889
+ ' backdrop.id = id + "_backdrop";' ,
890
+ ' backdrop.onclick = function() { exitWebRFullWidth(id); };' ,
891
+ ' document.body.appendChild(backdrop);' ,
892
+ ' backdrop.style.display = "block";' ,
893
+ ' container.classList.add("rocleteer-webr-fullwidth");' ,
894
+ ' document.body.style.overflow = "hidden";' ,
895
+ ' var toggleBtn = document.getElementById(id + "_toggle");' ,
896
+ ' if (toggleBtn) {' ,
897
+ ' toggleBtn.innerHTML = "\\ u274C Exit Full Screen";' ,
898
+ ' }' ,
899
+ ' }' ,
900
+ ' function exitWebRFullWidth(id) {' ,
901
+ ' var container = document.getElementById(id + "_container").closest(".rocleteer-webr-container");' ,
902
+ ' var backdrop = document.getElementById(id + "_backdrop");' ,
903
+ ' if (backdrop) {' ,
904
+ ' backdrop.remove();' ,
905
+ ' }' ,
906
+ ' container.classList.remove("rocleteer-webr-fullwidth");' ,
907
+ ' document.body.style.overflow = "";' ,
908
+ ' var toggleBtn = document.getElementById(id + "_toggle");' ,
909
+ ' if (toggleBtn) {' ,
910
+ ' toggleBtn.innerHTML = "\\ u26F6 Full width";' ,
911
+ ' }' ,
912
+ ' }' ,
680
913
' </script>'
681
914
)
682
915
0 commit comments