R/offline_html.r

Defines functions armd.offline.funs mathjax.to.offline.code slides.print.screen.css shiny.ui.to.html.document offline.shiny.headers as.inlined.mathjax.html findShinyRessourceDir str.start.equal inline.external.html armd.offline.html armd.offline.test examples.offline.html

examples.offline.html = function() {
  setwd("D:/libraries/armd/examples")
  outdir = paste0(getwd(),"/figures")

  name = "offline_test"
  rmd.file = paste0(name,".rmd")
  #am.file = paste0(name,".am")
  #html.file = paste0(name,".html")


  #am = fetch.am(rmd.file = rmd.file)
  am= parse.armd(file=rmd.file)

  #str(am$header.tags[[1]])
  html = armd.offline.html(am=am)

  ui = armd.slide.ui(am = am)

  rendered = htmltools::renderTags(ui)
  head = rendered$head
  body = rendered$html

  head = inline.external.html(head)
  body = inline.external.html(body)

  cat(head)
  cat(body)
}

armd.offline.test = function() {
  restore.point("create.offline.am")
  app = eventsApp()
  ui = armd.ui(am,add.page = TRUE)

  #ui = bootstrapPage(
  #  am$header.tags,
  #  ui
  #)
  app$ui = ui
  viewApp(app, launch.browser = TRUE)
}

armd.offline.html = function(am, outfile=paste0(am$name,"_offline.html"),print.outfile=paste0(am$name,"_offline_print.html"), browse=TRUE) {
  restore.point("create.offline.am")


  ui = armd.ui(am,add.page = FALSE)
  ui = tagList(
    tags$style(class="just_offline_print",HTML(slides.print.screen.css())),
    ui
  )
  html = as.inlined.mathjax.html(ui,use.button = FALSE)


  restore.point("create.offline.am.inner")

  org.html = html
  library(XML)
  library(selectr)
  h = htmlParse(html)

  nodes = querySelectorAll(h,".remove_offline, #MathJax_Message, .modebar modebar--hover")
  for(node in nodes) XML::removeNodes(node)

  del.files=c("MathJax.js","highlight.min.js","r.min.js","json2-min.js","shiny.css","shiny.min.js","shinyBS.css","shinyBS.js","babel-polyfill.min.js","font-awesome.min.css","plotly-latest.min.js",
#    "plotly.js","plotly-htmlwidgets.css",
    "crosstalk.min.js","crosstalk.css"
  )
  del.path = NULL
  html = inline.external.html(h=h,del.path=del.path,del.files=del.files)

  work.html = xml2html(h)

  nodes = querySelectorAll(h,".just_offline_print")
  for(node in nodes) XML::removeNodes(node)

  html = xml2html(h)

  if (!is.null(outfile)) {
    writeUtf8(merge.lines(html), outfile)
    if (browse)
      browseURL(outfile)
  }

  # don't make offline print anymore
  return(invisible(html))


  # make offline print html
  h = htmlParse(work.html)

  nodes = querySelectorAll(h,".inlined_script_bootstrap_min_js")
  for(node in nodes) XML::removeNodes(node)

  html = inline.external.html(h=h,del.files=del.files, just.delete = TRUE)

  nodes = querySelectorAll(h,".remove_offline_print")
  for(node in nodes) XML::removeNodes(node)

  nodes = querySelectorAll(h,"div")
  for(node in nodes) set.node.css(node,display="block")
  nodes = querySelectorAll(h,".slide-container-div")
  for(node in nodes) set.node.css(node, "padding-left"=NULL, "padding-right"=NULL)

  print.html = xml2html(h)
  if (!is.null(print.outfile)) {
    writeUtf8(merge.lines(print.html), print.outfile)
    if (browse)
      browseURL(print.outfile)
  }

  invisible(html)
}


inline.external.html = function(html=NULL, h=NULL, inline.script=TRUE, inline.css=TRUE, del.files=NULL, ignore.files=NULL, del.path=NULL, just.delete=FALSE) {
  restore.point("inline.external.html")

  library(XML)
  library(selectr)
  if (is.null(h))
    h = XML::htmlParse(html, asText=TRUE)

  ##################################################
  # inline external css
  ##################################################
  nodes = querySelectorAll(h, "link")
  src = unlist(lapply(nodes, function(node) {
    res = xmlAttrs(node)["href"]
    if (is.null(res)) return(NA)
    res
  }))
  rows = !is.na(src)
  nodes = nodes[rows]
  src = src[rows]

  res = findShinyRessourceDir(src)

  rows = !(res$path %in% del.path)
  for (node in nodes[!rows]) removeNodes(node)

  restore.point("dhfgdhfgzuegfzu")
  nodes = nodes[rows]
  dirs = res$dir[rows]
  paths = res$path[rows]



  for (i in seq_along(nodes)) {
    #restore.point("hskjfhuidffhu")
    node = nodes[[i]]
    dir  = dirs[[i]]
    path = paths[[i]]
    file = basename(dir)

    if (any(str.start.equal(file, del.files))) {
      removeNodes(node)
      next
    }
    if (any(str.start.equal(file, ignore.files))) {
      next
    }
    if (just.delete) next

    cat("\ninline style ", file)
    code = merge.lines(c(
      paste0("\n /* INLINED ",file,"*/ \n"),
      readLines(dir, warn = FALSE),
      paste0("\n /* END INLINED ",file,"*/ \n")
    ))
    code = mark_utf8(code)
    file_class = gsub(".","_",file,fixed=TRUE)
    replace.link.with.style.node(node,code, attr=c(class=paste0("inlined_style inlined_style_", file_class)))
  }

  ##################################################
  # inline external scripts
  ##################################################
  nodes = querySelectorAll(h, "script")
  src = unlist(lapply(nodes, function(node) {
    res = xmlAttrs(node)["src"]
    if (is.null(res)) return(NA)
    res
  }))

  rows = !is.na(src)
  nodes = nodes[rows]
  src = src[rows]

  res = findShinyRessourceDir(src)

  rows = !((res$path %in% del.path) | has.substr(src,"mathjax") | has.substr(src,"plotly-latest.min.js"))

  # remove external scripts
  for (node in nodes[!rows]) removeNodes(node)

  nodes = nodes[rows]
  dirs = res$dir[rows]
  paths = res$path[rows]

  for (i in seq_along(nodes)) {
    node = nodes[[i]]
    dir  = dirs[[i]]
    path = paths[[i]]
    file = basename(dir)

    if (any(str.start.equal(file, del.files))) {
      removeNodes(node)
      next
    }
    if (any(str.start.equal(file, ignore.files))) {
      next
    }
    if (just.delete) next
    #restore.point("nhfbrbfrbf")

    code = merge.lines(c(
      paste0("\n /* INLINED ",file,"*/ \n"),
      readLines(dir, warn = FALSE,encoding = "UTF-8"),
      paste0("\n /* END INLINED ",file,"*/ \n")
    ))
    code = mark_utf8(code)
    cat("\ninline script ", file)
    file_class = gsub(".","_",file,fixed=TRUE)

    try(replace.script.node(node,code, attr=c(class=paste0("inlined_script inlined_script_", file_class))))
  }

  xml2html(h)
}

str.start.equal = function(str, vec) {
  if (length(vec)==0) return(NULL)
  short = substring(str,1,nchar(vec))
  short == vec
}

findShinyRessourceDir = function(src) {
  restore.point("findShinyRessourceDir")


  # get path name
  names = str.left.of(src,"/")

  # distinguish local and web ressources
  is.web = str.starts.with(names,"http") & has.substr(names,":")

  loc.src = src[!is.web]
  names = names[!is.web]
  right = str.right.of(loc.src,"/")

  sr = shiny:::.globals$resources
  if (!is.null(sr)) {
    # Older shiny version
    dirs = sapply(sr[names], function(sr) sr$directoryPath)
  } else {
    # Newer shiny version
    sr = shiny:::.globals$resourcePaths
    dirs = sapply(sr[names], function(sr) sr$path)
  }
  dirs[names=="shared"] = paste0(path.package("shiny"),"/www/shared")
  dirs = file.path(dirs,right)
  src[!is.web] = dirs
  path = src
  path[!is.web] = names

  list(dir=src, is.web =is.web, path=path)
}



#' Takes a shiny tagList and returns an html txt
#' in which mathjax is inlined
as.inlined.mathjax.html = function(x, page.fun = bootstrapPage, launch.browser=TRUE, use.button=FALSE) {
  restore.point("ui.to.inlined.mathjax.html")

  if (is.character(x)) x = HTML(x)
  ui = page.fun(
    mathjax.to.offline.code(container.id = NULL, use.button=use.button),
    with.mathjax(x)
  )
  app = eventsApp(adapt.ui = FALSE)
  app$ui = ui

  eventHandler(eventId="downloadHtmlPage", id=NULL, fun=function(...) {
    args = list(...)
    restore.point("downloadHtmlPage")
    html = args$html
    Encoding(html) = "UTF-8"
    getApp()$session$sendCustomMessage(type = "closeWindow", message = "message")
    stopApp(invisible(html))
  })
  html = viewApp(app,launch.browser = launch.browser)
}

offline.shiny.headers = function() {
  tagList(
    tags$head(includeScript(paste0(path.package("shiny"),"/www/shared/jquery.min.js"))),
    tags$head(includeCSS(paste0(path.package("shiny"),"/www/shared/bootstrap/css/bootstrap.css"))),
    tags$head(includeScript(paste0(path.package("shiny"),"/www/shared/bootstrap/js/bootstrap.min.js")))
  )
}

shiny.ui.to.html.document = function(ui) {
  restore.point("shiny.ui.to.html")

  rendered = htmltools::renderTags(ui)
  html = simple.html.page(head=rendered$head, body = rendered$html)
  html
}


slides.print.screen.css = function() {
  '
  body {
    margin-left: 10%;
    margin-right: 10%;
  }
  '

}


mathjax.to.offline.code = function(container.id=NULL, use.button=TRUE) {

  inner.target = if (is.null(container.id)) {
    'document.documentElement'
  } else {
    paste0('document.getElementById("',container.id,'")')
  }

  js = paste0('
  Shiny.addCustomMessageHandler("closeWindow", function(m) {window.close();});

  function utf8_to_b64( str ) {
    return window.btoa(unescape(encodeURIComponent( str )));
  }
  function remove_mathjax_and_save() {
    $(".mathjax_load").remove();
    $(".mathjax_typeset").remove();
    $(".MJX_Assistive_MathML").remove();
    $(".remove_me").remove();
    $("#saveOfflineDiv").remove();
    Shiny.onInputChange("downloadHtmlPage", {id: "downloadHtmlPage", html: ', inner.target,'.innerHTML});
  }
  ')

  if (use.button) {
    js = paste0(js,'
$("#saveOfflineBtn").click(function(e) {
  remove_mathjax_and_save();
});'
    )
    btn = actionButton("saveOfflineBtn", "Save as offline html")
  } else {
    js = paste0(js,'
MathJax.Hub.Queue(function () {
  //alert("remove_mathjax_and_save");
  remove_mathjax_and_save();

});'
    )
    btn = NULL
  }

  tagList(
    div(id="saveOfflineDiv", class="remove_me",
      btn,
      tags$script(js)
    )

  )

}

armd.offline.funs = function(...) {
  list(
    ## plotly functions
    ggplotly = function(x,...) {
      x
    },
    config = function(x,...) {
      x
    }
  )

}
skranz/armd documentation built on Sept. 4, 2020, 12:22 p.m.