diff --git a/.Rbuildignore b/.Rbuildignore index e6c58d3..7ff8da5 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -10,3 +10,7 @@ ^docs$ ^pkgdown$ ^\.github$ +^public$ +^LICENSE\.md$ +^srcjs$ +^token\.rds$ diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml index a7276e8..c9f0165 100644 --- a/.github/workflows/pkgdown.yaml +++ b/.github/workflows/pkgdown.yaml @@ -11,6 +11,8 @@ on: name: pkgdown +permissions: read-all + jobs: pkgdown: runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 13b584f..f805bdb 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ .quarto inst/doc docs +token.rds + +config.yml diff --git a/DESCRIPTION b/DESCRIPTION index bad2fbe..47601fd 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -6,19 +6,51 @@ Authors@R: comment = c(ORCID = "0000-0002-7489-8787")) Description: No Clocks, LLC packaged assets and workflows License: MIT + file LICENSE -Encoding: UTF-8 -Language: en-US -Roxygen: list(markdown = TRUE) -RoxygenNote: 7.3.0 +URL: https://noclocks.github.io/noclocksR/, + https://docs.noclocks.dev/noclocksR/ +Depends: + R (>= 2.10) Imports: - tibble + cli, + config, + covr, + covrpage, + digest, + dplyr, + fs, + gargle, + gitdown, + glue, + here, + htmltools, + httr, + httr2, + jsonlite, + keyring, + markdown, + metathis, + parallel, + pkgdown, + pkgload, + purrr, + rlang, + stringr, + testdown, + tibble, + tibblify, + tidyr, + utils, + xml2 Suggests: knitr, rmarkdown, testthat (>= 3.0.0) -VignetteBuilder: knitr -URL: https://noclocks.github.io/noclocksR/ +VignetteBuilder: + knitr +Config/Needs/website: noclocks/noclocksR Config/testthat/edition: 3 -Depends: - R (>= 2.10) +Encoding: UTF-8 +Language: en-US LazyData: true +Roxygen: list(markdown = TRUE) +RoxygenNote: 7.3.1 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..66cb04b --- /dev/null +++ b/LICENSE @@ -0,0 +1,2 @@ +YEAR: 2024 +COPYRIGHT HOLDER: No Clocks, LLC diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..0d3e995 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +# MIT License + +Copyright (c) 2024 No Clocks, LLC + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/NAMESPACE b/NAMESPACE index f9c55c8..233e271 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,10 +1,87 @@ # Generated by roxygen2: do not edit by hand +export(abort) +export(add_gitignore) +export(ansi_strip) +export(build_pkgdown_with_reports) +export(cnd_entrace) +export(color_palette) +export(detectCores) +export(digest) +export(document_dataset) +export(download_logo) +export(entrace) +export(error_cnd) +export(fetch_brand) +export(get_favicon) +export(get_gitignore) +export(get_logo_file_name) export(shiny_resume_body) export(shiny_resume_navbar) export(shiny_resume_page) +export(trace_back) +export(tree) +export(typography) +export(use_noclocks_meta) +import(htmltools) +import(pkgdown) +importFrom(cli,ansi_strip) +importFrom(cli,tree) +importFrom(config,get) +importFrom(covr,package_coverage) +importFrom(covr,report) +importFrom(covrpage,covrpage) +importFrom(digest,digest) +importFrom(dplyr,mutate) +importFrom(dplyr,pull) +importFrom(fs,dir_create) +importFrom(fs,dir_exists) +importFrom(fs,file_move) +importFrom(fs,path) +importFrom(fs,path_package) +importFrom(gargle,gargle_oauth_client_from_json) +importFrom(gargle,init_AuthState) +importFrom(gitdown,git_down) +importFrom(glue,glue) importFrom(htmltools,HTML) importFrom(htmltools,htmlDependency) importFrom(htmltools,tagList) importFrom(htmltools,tags) +importFrom(httr,GET) +importFrom(httr,content) +importFrom(httr2,req_auth_bearer_token) +importFrom(httr2,req_headers) +importFrom(httr2,req_method) +importFrom(httr2,req_perform) +importFrom(httr2,req_url_path_append) +importFrom(httr2,request) +importFrom(httr2,resp_body_json) +importFrom(jsonlite,fromJSON) +importFrom(markdown,markdownToHTML) +importFrom(metathis,meta) +importFrom(metathis,meta_social) +importFrom(parallel,detectCores) +importFrom(pkgdown,build_site) +importFrom(pkgload,pkg_name) +importFrom(purrr,map_chr) +importFrom(purrr,pluck) +importFrom(purrr,pmap_chr) +importFrom(rlang,abort) +importFrom(rlang,cnd_entrace) +importFrom(rlang,entrace) +importFrom(rlang,error_cnd) +importFrom(rlang,trace_back) +importFrom(stringr,str_replace_all) +importFrom(stringr,str_to_lower) +importFrom(testdown,test_down) importFrom(tibble,tibble) +importFrom(tibblify,tib_chr) +importFrom(tibblify,tib_dbl) +importFrom(tibblify,tib_df) +importFrom(tibblify,tib_lgl) +importFrom(tibblify,tib_row) +importFrom(tibblify,tib_unspecified) +importFrom(tibblify,tibblify) +importFrom(tibblify,tspec_object) +importFrom(tidyr,unnest) +importFrom(utils,assignInMyNamespace) diff --git a/R/assets.R b/R/assets.R new file mode 100644 index 0000000..e69de29 diff --git a/R/auth0_jwt.R b/R/auth0_jwt.R new file mode 100644 index 0000000..ad0ca2b --- /dev/null +++ b/R/auth0_jwt.R @@ -0,0 +1,47 @@ +get_auth0_jwt <- function( + auth0_config = config::get("auth0"), + ... +) { + + if (is.null(auth0_config)) { + rlang::abort("No Auth0 configuration found") + } + + httr2::curl_translate( + "curl --request POST --url https://jimbrig.us.auth0.com/oauth/token --header 'content-type: application/json' --data '{\"client_id\":\"tCLHUNuGdBXlaeZ94OrHfjVOUVuFPRte\",\"client_secret\":\"te2n7aYvXPYflth3OFMi8QK5c7obtjoJPNxfBfs1LKQIx2j9YfobR2rPKWsbEz-3\",\"audience\":\"https://auth-api.noclocks.dev\",\"grant_type\":\"client_credentials\"}'" + ) + + base_url <- auth0_config$jwt_url + client_id <- auth0_config$client_id + client_secret <- auth0_config$client_secret + audience <- auth0_config$audience + grant_type <- auth0_config$grant_type + + req <- httr2::request( + base_url = base_url + ) |> + httr2::req_method("POST") |> + httr2::req_headers( + `Content-Type` = "application/json" + ) |> + httr2::req_body_json( + list( + client_id = client_id, + client_secret = client_secret, + audience = audience, + grant_type = grant_type + ) + ) + + res <- req |> httr2::req_perform() + + if (res$status_code != 200) { + rlang::abort("Auth0 JWT request failed") + } + + content <- res |> + httr2::resp_body_json() + + content + +} diff --git a/R/brandfetch.R b/R/brandfetch.R new file mode 100644 index 0000000..c338d62 --- /dev/null +++ b/R/brandfetch.R @@ -0,0 +1,198 @@ + +#' Fetch a Brand using the Brandfetch API +#' +#' @description +#' This function fetches a brand using the +#' [Brandfetch Brand API](https://docs.brandfetch.com/reference/brand-api). +#' +#' @param domain The domain of the brand to fetch +#' @param brandfetch_api_key The API key for the Brandfetch API +#' @param ... Additional arguments +#' +#' @return A tibble with the brand information +#' @export +#' +#' @importFrom httr2 request req_url_path_append req_method req_auth_bearer_token req_headers req_perform +#' @importFrom tibblify tibblify tspec_object tib_chr tib_lgl tib_dbl tib_unspecified tib_df tib_row +#' @importFrom purrr pluck +#' @importFrom tidyr unnest +#' @importFrom rlang abort +#' @importFrom config get +#' @importFrom tibble tibble +#' @importFrom httr2 resp_body_json +#' @importFrom dplyr pull +fetch_brand <- function( + domain, + brandfetch_api_key = Sys.getenv("BRANDFETCH_API_KEY", unset = config::get("brandfetch_api_key")), + ... +) { + + base_url <- "https://api.brandfetch.io/v2/brands" + + if (is.null(brandfetch_api_key)) { + brandfetch_api_key <- config::get("brandfetch_api_key") + } + + if (is.null(brandfetch_api_key)) { + rlang::abort("No Brandfetch API key found") + } + + req <- httr2::request( + base_url = base_url + ) |> + httr2::req_url_path_append( + domain + ) |> + httr2::req_method("GET") |> + httr2::req_auth_bearer_token(brandfetch_api_key) |> + httr2::req_headers( + `Accept` = "application/json", + `Content-Type` = "application/json" + ) + + res <- req |> httr2::req_perform() + if (res$status_code != 200) { + rlang::abort("Brandfetch API request failed") + } + + content <- res |> + httr2::resp_body_json() + + spec <- tibblify::tspec_object( + tibblify::tib_chr("id"), + tibblify::tib_chr("name"), + tibblify::tib_chr("domain"), + tibblify::tib_lgl("claimed"), + tibblify::tib_chr("description"), + tibblify::tib_chr("longDescription"), + tibblify::tib_dbl("qualityScore"), + tibblify::tib_unspecified("images"), + + tibblify::tib_df( + "links", + tibblify::tib_chr("name"), + tibblify::tib_chr("url") + ), + + tibblify::tib_df( + "logos", + tibblify::tib_chr("theme"), + tibblify::tib_df( + "formats", + tibblify::tib_chr("src"), + tibblify::tib_unspecified("background"), + tibblify::tib_chr("format"), + tibblify::tib_int("height"), + tibblify::tib_int("width"), + tibblify::tib_int("size"), + ), + tibblify::tib_unspecified("tags"), + tibblify::tib_chr("type") + ), + + tibblify::tib_df( + "colors", + tibblify::tib_chr("hex"), + tibblify::tib_chr("type"), + tibblify::tib_int("brightness"), + ), + + tibblify::tib_df( + "fonts", + tibblify::tib_chr("name"), + tibblify::tib_chr("type"), + tibblify::tib_chr("origin"), + tibblify::tib_chr("originId"), + tibblify::tib_unspecified("weights"), + ), + + tibblify::tib_row( + "company", + tibblify::tib_unspecified("employees"), + tibblify::tib_unspecified("foundedYear"), + tibblify::tib_unspecified("kind"), + tibblify::tib_unspecified("location"), + tibblify::tib_df( + "industries", + tibblify::tib_unspecified("id", required = FALSE), + tibblify::tib_unspecified("parent"), + tibblify::tib_dbl("score", required = FALSE), + tibblify::tib_chr("name", required = FALSE), + tibblify::tib_chr("emoji", required = FALSE), + tibblify::tib_chr("slug", required = FALSE) + ) + ) + ) + + out <- tibblify::tibblify(content, spec, unspecified = "drop") + out$logos <- out$logos |> tidyr::unnest("formats") + out$company <- out$company |> purrr::pluck("industries") + + return(out) + +} + + +# out_company <- tibble::tibble( +# id = content$id, +# name = content$name, +# domain = domain, +# claimed = content$claimed, +# description = content$description, +# longDescription = content$longDescription, +# links = content$links |> purrr::map_dfr( +# ~ tibble::tibble( +# name = .x$name, +# url = .x$url +# ) +# ), +# qualityScore = content$qualityScore, +# company = content$company |> purrr::map_dfr( +# ~ tibble::tibble( +# industries = .x$industries |> purrr::map_dfr( +# ~ tibble::tibble( +# score = .x$score, +# id = .x$id, +# name = .x$name, +# emoji = .x$emoji, +# parent = .x$parent |> purrr::map_dfr( +# ~ tibble::tibble( +# emoji = .x$emoji, +# id = .x$id, +# name = .x$name, +# slug = .x$slug +# ) +# ), +# slug = .x$slug +# ) +# ), +# kind = .x$kind, +# location = .x$location +# ) +# ) +# +# ) + +# out_logos <- content$logos |> purrr::map_dfr( +# ~ tibble::tibble( +# domain = domain, +# theme = .x$theme, +# src = .x$formats$src, +# background = .x$formats$background, +# format = .x$formats$format, +# height = .x$formats$height, +# width = .x$formats$width, +# size = .x$formats$size, +# tags = .x$tags, +# type = .x$type +# ) +# ) +# +# out_colors <- content$colors |> purrr::map_dfr( +# ~ tibble::tibble( +# domain = domain, +# hex = .x$hex, +# type = .x$type, +# brightness = .x$brightness +# ) +# ) diff --git a/R/colors.R b/R/colors.R index e69de29..e0d0b60 100644 --- a/R/colors.R +++ b/R/colors.R @@ -0,0 +1,21 @@ + +#' No Clocks Color Palettes +#' +#' @description +#' Collection of color palettes for use in No Clocks, LLC projects. +.noclocks_colors <- list( + noclocks_blue = "#1A1A1A", + noclocks_green = "#1A1A1A", + noclocks_red = "#1A1A1A", + noclocks_yellow = "#1A1A1A", + noclocks_orange = "#1A1A1A", + noclocks_purple = "#1A1A1A", + noclocks_pink = "#1A1A1A", + noclocks_brown = "#1A1A1A", + noclocks_gray = "#1A1A1A", + noclocks_black = "#1A1A1A", + noclocks_white = "#1A1A1A" +) + + + diff --git a/R/data.R b/R/data.R new file mode 100644 index 0000000..6647f37 --- /dev/null +++ b/R/data.R @@ -0,0 +1,16 @@ +#' http_status_codes +#' +#' @description +#' A dataset containing HTTP status codes and their corresponding messages. +#' +#' @source +#' https://en.wikipedia.org/wiki/List_of_HTTP_status_codes +#' +#' @format A data.frame with 63 rows and 3 columns: +#' \describe{ +#' \item{\code{code}}{integer. The HTTP Status Code.} +#' \item{\code{message}}{character. The HTTP Status Message.} +#' \item{\code{category}}{character. The HTTP Status Category.} +#'} +"http_status_codes" + diff --git a/R/document_dataset.R b/R/document_dataset.R new file mode 100644 index 0000000..951a28a --- /dev/null +++ b/R/document_dataset.R @@ -0,0 +1,134 @@ +#' Document a Dataset +#' +#' @description +#' This function generates a `roxygen2` skeleton for documenting a dataset. +#' +#' @param data_obj (Required) The dataset to document. Should be a `data.frame` +#' or `tibble`. +#' @param name (Optional) The name of the dataset. Default is `DATASET_NAME` if +#' not provided. +#' @param description (Optional) A description of the dataset. Default is +#' `DATASET_DESCRIPTION` if not provided. +#' @param source (Optional) The source of the dataset. Default is `DATASET_SOURCE` +#' if not provided. +#' @param col_types (Optional) A character vector of column types. Default is +#' the types of the columns in the dataset. +#' @param col_descs (Optional) A character vector of column descriptions. Default +#' is `COLUMN_DESCRIPTION` for each column. +#' @param file (Optional) The file to write the documentation to. Default is +#' `R/data.R`. +#' @param append (Optional) Append to the file if it exists. Default is `TRUE`. +#' If `FALSE`, the file will be overwritten, unless `overwrite` is `TRUE`, then +#' the function does not write to a file. +#' @param overwrite (Optional) Overwrite the file if it exists. Default is `FALSE`. +#' @param ... Additional arguments +#' +#' @return Invisibly returns a character string with the `roxygen2` +#' documentation skeleton for the dataset. +#' @export +#' +#' @importFrom fs path +#' @importFrom purrr map_chr +#' @importFrom glue glue +#' +#' @examples +#' data(mtcars) +#' document_dataset( +#' mtcars, +#' name = "mtcars", +#' description = "Motor Trend Car Road Tests", +#' source = "Henderson and Velleman (1981)", +#' col_descs = c( +#' "Miles/(US) gallon", +#' "Number of cylinders", +#' "Displacement (cu.in.)", +#' "Gross horsepower", +#' "Rear axle ratio", +#' "Weight (1000 lbs)", +#' "1/4 mile time", +#' "V/S" +#' ), +#' file = fs::path_temp("mtcars.R") +#' ) +#' +#' file.edit(fs::path_temp("mtcars.R")) +document_dataset <- function( + data_obj, + name = "DATASET_NAME", + description = "DATASET_DESCRIPTION", + source = "DATASET_SOURCE", + col_types = purrr::map_chr(data_obj, typeof), + col_descs = rep("COLUMN_DESCRIPTION", length(names(data_obj))), + file = fs::path("R", "data.R"), + append = TRUE, + overwrite = FALSE, + ... +) { + + # Ensure column names are available + col_names <- names(data_obj) + if (is.null(col_names)) { + stop("The data object must have column names.") + } + + # Validate col_types + if (missing(col_types) || is.null(col_types)) { + col_types <- purrr::map_chr(data_obj, typeof) + } + + # Validate col_descs + if (missing(col_descs) || is.null(col_descs)) { + col_descs <- rep("COLUMN_DESCRIPTION", length(col_names)) + } + + # Ensure col_types and col_descs match the number of columns + stopifnot( + length(col_types) == length(col_names), + length(col_descs) == length(col_names) + ) + + # Generate column descriptions + col_roxys <- glue::glue( + .open = "[[", + .close = "]]", + "#' \\item{\\code{[[col_names]]}}{[[col_types]]. [[col_descs]].}" + ) |> paste(collapse = "\n") + + # Determine dataset dimensions + dims <- paste0(nrow(data_obj), " rows and ", ncol(data_obj), " columns") + + # Prepare roxygen2 documentation skeleton + pre <- glue::glue( + .sep = "\n", + "#' {name}", + "#'", + "#' @description", + "#' {description}", + "#'", + "#' @source", + "#' {source}", + "#'", + "#' @format A data frame with {dims}:" + ) + + skeleton <- paste0( + pre, + "\n", + "#' \\describe{\n", + col_roxys, + "\n", + "#'}\n", + '"', name, '"\n' + ) + + # Handle file writing based on append and overwrite flags + if (overwrite && append) { + stop("Cannot both append and overwrite. Please choose one.") + } + + if (overwrite && file.exists(file)) { + file.remove(file) + } + + cat(skeleton, file = file, append = append, sep = "\n") +} diff --git a/R/favicon.R b/R/favicon.R new file mode 100644 index 0000000..cd8bb6c --- /dev/null +++ b/R/favicon.R @@ -0,0 +1,116 @@ +#' Get Favicon from a URL +#' +#' @description +#' This function retrieves the favicon from provided URL or vector of URLs. +#' +#' @param url A character vector of URLs. +#' @param out_file A single path to a folder to save the favicons, or a +#' character vector of file paths to save each individual favicon to. +#' @param fallback (optional) function to fallback to if favicon is not found. +#' +#' @return A character vector of file paths to the favicons. +#' +#' @export +get_favicon <- function( + url, + out_file = NULL, + fallback = get_favicon.ddg +) { + + if (is.null(out_file)) { + out_file <- nullfile() + } + + if (!is.character(url)) { + rlang::abort("`url` must be a character vector.") + } + + purrr::map_chr(url, ~{ + favicon_url <- get_favicon_ico(.x, out_file) + if (is.null(favicon)) { + favicon <- fallback(.x) + } + if (is.null(favicon)) { + return(NULL) + } + if (is.null(out_file)) { + return(favicon) + } + file_path <- fs::path(out_file, basename(favicon)) + utils::download.file(favicon, file_path, mode = "wb") + return(file_path) + }) + + +} + +read_html <- function(url) { + xml2::read_html(url(url), silent = TRUE) +} + +get_favicon <- function(url) { + + parsed_url <- httr2::url_parse(url) + scheme <- parsed_url$scheme + server <- parsed_url$server + path <- parsed_url$path + + if (scheme == "file") { + path <- fs::path_expand(path) + raw <- read_html(path) + } else { + raw <- read_html(url) + } + + xpath <- "/html/head/link[@rel = 'icon' or @rel = 'shortcut icon']" + link_element <- xml2::xml_find_first(raw, xpath) + href <- xml2::xml_attr(link_element, "href") + if (is.na(href)) return("") + + base_element <- xml2::xml_find_first(raw, "/html/head/base") + base_link <- xml2::xml_attr(base_element, "href") + if (!is.na(base_link)) { href <- paste0(base_link, href) } + + return(href) +} + +get_favicon.ddg <- function(url) { + + domain <- httr2::url_parse(url)$hostname + + if (is.null(domain)) { + return(NULL) + } + + paste0("https://icons.duckduckgo.com/ip3/", domain, ".ico") + +} + +get_favicon_ico <- function( + url, + path, + method = getOption("download.file.method", "auto"), + extra = getOption("download.file.extra", NULL), + headers = NULL +) { + + favicon_url <- paste0(url, "/favicon.ico") + + res <- tryCatch( + suppressWarnings( + utils::download.file( + url = favicon_url, + destfile = path, + method = method, + quiet = TRUE, + extra = extra, + headers = headers + ) + ), + error = function(e) return(1) + ) + + if (res == 0) return(favicon_url) else return(NULL) + +} + diff --git a/R/flyio.R b/R/flyio.R new file mode 100644 index 0000000..e69de29 diff --git a/R/gcp.R b/R/gcp.R new file mode 100644 index 0000000..e69de29 diff --git a/R/gitconfig.R b/R/gitconfig.R index 360bb42..3711611 100644 --- a/R/gitconfig.R +++ b/R/gitconfig.R @@ -1,41 +1,41 @@ -git_config_get <- function(key) { - - git_config <- gert::git_config_global() - - keys <- unique(gitconfig$name) - - if (!key %in% keys) { - warning(glue::glue( - "The key '{key}' is not in your git config. Please set it before proceeding." - )) - return(NULL) - } - - git_config |> - dplyr::filter( - level %in% c("global", "xdg"), - name == key - ) |> - dplyr::pull(value) |> - unique() -} - - -assert_git_config <- function() { - - gitconfig <- gert::git_config_global() - - if (is.null(gitconfig)) { - stop("Please set your git config before proceeding.") - } - - user_name <- git_config_get("user.name") - user_email <- git_config_get("user.email") - default_branch <- git_config_get("init.defaultbranch") - signing_key <- git_config_get("user.signingkey") - - if (is.null(user_name) | is.null(user_email) | is.null(default_branch) | is.null(signing_key)) { - stop("Please set your git config before proceeding.") - } -} - +# git_config_get <- function(key) { +# +# git_config <- gert::git_config_global() +# +# keys <- unique(gitconfig$name) +# +# if (!key %in% keys) { +# warning(glue::glue( +# "The key '{key}' is not in your git config. Please set it before proceeding." +# )) +# return(NULL) +# } +# +# git_config |> +# dplyr::filter( +# level %in% c("global", "xdg"), +# name == key +# ) |> +# dplyr::pull(value) |> +# unique() +# } +# +# +# assert_git_config <- function() { +# +# gitconfig <- gert::git_config_global() +# +# if (is.null(gitconfig)) { +# stop("Please set your git config before proceeding.") +# } +# +# user_name <- git_config_get("user.name") +# user_email <- git_config_get("user.email") +# default_branch <- git_config_get("init.defaultbranch") +# signing_key <- git_config_get("user.signingkey") +# +# if (is.null(user_name) | is.null(user_email) | is.null(default_branch) | is.null(signing_key)) { +# stop("Please set your git config before proceeding.") +# } +# } +# diff --git a/R/gitignore.R b/R/gitignore.R index 88a16d8..69eb50f 100644 --- a/R/gitignore.R +++ b/R/gitignore.R @@ -1,16 +1,28 @@ -gitignore_includes <- c( - "secrets", - "windows", - "r", - "python", - "node", - "markdown" -) - - - +#' `.gitignore` Templates +#' +#' @description +#' Get `.gitignore` templates from [Toptal](https://www.toptal.com/developers/gitignore). +#' +#' @param template (character) The name of the `.gitignore` template +#' @param as (character) The format to return the `.gitignore` template. Can be +#' `list` or `json`. Default is `list`. +#' +#' @return The `.gitignore` template as a list or JSON +#' @export +#' +#' @importFrom httr GET content +#' @importFrom jsonlite fromJSON get_gitignore <- function(template, as = c("list", "json")) { + gitignore_includes <- c( + "secrets", + "windows", + "r", + "python", + "node", + "markdown" + ) + if (!template %in% gitignore_templates) { stop("Invalid gitignore template") } @@ -26,6 +38,17 @@ get_gitignore <- function(template, as = c("list", "json")) { } } +#' Add `.gitignore` Templates +#' +#' @param includes (character) The `.gitignore` templates to include +#' @param path (character) The path to the `.gitignore` file. Default is `getwd()` +#' @param ... Additional arguments +#' +#' @return The `.gitignore` file with the templates included +#' @export +#' +#' @importFrom httr GET content +#' @importFrom jsonlite fromJSON add_gitignore <- function(includes, path = getwd(), ...) { gitignore_file <- file.path(path, ".gitignore") diff --git a/R/graphql.R b/R/graphql.R index 8b0d986..f1a5ddb 100644 --- a/R/graphql.R +++ b/R/graphql.R @@ -1,156 +1,156 @@ -#' Query GraphQL Github API +#' #' Query GraphQL Github API +#' #' +#' #' This function helps you retrieving the status of a project board within the organization +#' #' +#' #' @param board_url url of the github project board +#' #' @param github_token access token to the graphql api +#' #' +#' #' @importFrom stringr str_extract +#' #' @importFrom glue glue +#' #' @importFrom gh gh_gql +#' #' @importFrom purrr map_dfr map_chr +#' #' @importFrom gitlabr multilist_to_tibble +#' #' @importFrom tidyr unnest_wider +#' #' @importFrom dplyr mutate select +#' #' +#' #' @return a tibble +#' #' +#' #' @export +#' #' @examples +#' #' \dontrun{ +#' #' +#' #' # Example with board hosted in an organization github account +#' #' board_url_organization <- "https://github.com/orgs/ThinkR-open/projects/4/" +#' #' github_token <- Sys.getenv("GITHUB_PAT") +#' #' +#' #' graphql_to_tibble( +#' #' board_url = board_url_organization, +#' #' github_token = github_token +#' #' ) +#' #' +#' #' +#' #' # Example with board hosted in a user github account +#' #' board_url_user <- "https://github.com/users/the-thinkr/projects/1" +#' #' github_token <- Sys.getenv("GITHUB_PAT") +#' #' +#' #' graphql_to_tibble( +#' #' board_url = board_url_user, +#' #' github_token = github_token +#' #' ) +#' #' } +#' graphql_to_tibble <- function(board_url, github_token = Sys.getenv("GITHUB_PAT")) { +#' if (github_token == "") { +#' stop("You must provide an access token to the Github Api (read:project scope is required)") +#' } #' -#' This function helps you retrieving the status of a project board within the organization +#' # extract organization and project number from board url to retrieve the node ID of the +#' # project within the organization +#' project_number <- str_extract( +#' string = board_url, +#' pattern = "(?<=projects/)[:digit:]{1,}" +#' ) #' -#' @param board_url url of the github project board -#' @param github_token access token to the graphql api +#' organization_1 <- +#' str_extract(string = board_url, pattern = "(?<=github.com/)[:graph:]{1,}(?=/projects/)") +#' organization <- gsub("orgs/|users/", "", organization_1) +#' if (grepl("^orgs/", organization_1)) { +#' is_orgs_or_user <- "orgs" +#' organization <- gsub("orgs/|users/", "", organization_1) +#' req_project_id <- glue( +#' ' +#' query{ +#' organization(login: "$_organization_$"){ +#' projectV2(number: $_project_number_$) { +#' id +#' } +#' } +#' }', +#' .open = "$_", +#' .close = "_$" +#' ) +#' } else if (grepl("^users/", organization_1)) { +#' is_orgs_or_user <- "users" +#' organization <- gsub("orgs/|users/", "", organization_1) +#' req_project_id <- +#' req_project_id <- glue( +#' ' +#' query{ +#' user(login: "$_organization_$"){ +#' projectV2(number: $_project_number_$) { +#' id +#' } +#' } +#' }', +#' .open = "$_", +#' .close = "_$" +#' ) +#' } #' -#' @importFrom stringr str_extract -#' @importFrom glue glue -#' @importFrom gh gh_gql -#' @importFrom purrr map_dfr map_chr -#' @importFrom gitlabr multilist_to_tibble -#' @importFrom tidyr unnest_wider -#' @importFrom dplyr mutate select +#' project_id <- gh_gql(req_project_id, .token = github_token) %>% +#' unlist() #' -#' @return a tibble #' -#' @export -#' @examples -#' \dontrun{ +#' # check whether the provided token has the right scope +#' if ("errors.message" %in% names(project_id)) { +#' stop(project_id["errors.message"]) +#' } #' -#' # Example with board hosted in an organization github account -#' board_url_organization <- "https://github.com/orgs/ThinkR-open/projects/4/" -#' github_token <- Sys.getenv("GITHUB_PAT") #' -#' graphql_to_tibble( -#' board_url = board_url_organization, -#' github_token = github_token -#' ) +#' # get project board status +#' req_board_infos <- glue( +#' 'query{ +#' node(id: "$_project_id_$") { +#' ... on ProjectV2 { +#' items(first: 100) { +#' nodes{ +#' id +#' fieldValues(first: 100) { +#' nodes{ +#' ... on ProjectV2ItemFieldSingleSelectValue { +#' name +#' field { +#' ... on ProjectV2FieldCommon { +#' name +#' } +#' } +#' } +#' } +#' } +#' content{ +#' ...on Issue { +#' title +#' url +#' number +#' state +#' createdAt +#' updatedAt +#' closedAt +#' } +#' } +#' } +#' } +#' } +#' } +#' }', +#' .open = "$_", +#' .close = "_$" +#' ) #' +#' board_infos <- gh_gql( +#' req_board_infos, +#' .token = github_token +#' ) #' -#' # Example with board hosted in a user github account -#' board_url_user <- "https://github.com/users/the-thinkr/projects/1" -#' github_token <- Sys.getenv("GITHUB_PAT") +#' board_infos <- board_infos$data$node$items$nodes #' -#' graphql_to_tibble( -#' board_url = board_url_user, -#' github_token = github_token -#' ) +#' +#' # export to tibble +#' board_tbl <- map_dfr(board_infos, function(issue) { +#' multilist_to_tibble(issue) %>% +#' unnest_wider(content) %>% +#' mutate(board_column = unlist(fieldValues[[1]])[1]) %>% +#' select(-fieldValues) +#' }) +#' +#' return(board_tbl) #' } -graphql_to_tibble <- function(board_url, github_token = Sys.getenv("GITHUB_PAT")) { - if (github_token == "") { - stop("You must provide an access token to the Github Api (read:project scope is required)") - } - - # extract organization and project number from board url to retrieve the node ID of the - # project within the organization - project_number <- str_extract( - string = board_url, - pattern = "(?<=projects/)[:digit:]{1,}" - ) - - organization_1 <- - str_extract(string = board_url, pattern = "(?<=github.com/)[:graph:]{1,}(?=/projects/)") - organization <- gsub("orgs/|users/", "", organization_1) - if (grepl("^orgs/", organization_1)) { - is_orgs_or_user <- "orgs" - organization <- gsub("orgs/|users/", "", organization_1) - req_project_id <- glue( - ' - query{ - organization(login: "$_organization_$"){ - projectV2(number: $_project_number_$) { - id - } - } - }', - .open = "$_", - .close = "_$" - ) - } else if (grepl("^users/", organization_1)) { - is_orgs_or_user <- "users" - organization <- gsub("orgs/|users/", "", organization_1) - req_project_id <- - req_project_id <- glue( - ' - query{ - user(login: "$_organization_$"){ - projectV2(number: $_project_number_$) { - id - } - } - }', - .open = "$_", - .close = "_$" - ) - } - - project_id <- gh_gql(req_project_id, .token = github_token) %>% - unlist() - - - # check whether the provided token has the right scope - if ("errors.message" %in% names(project_id)) { - stop(project_id["errors.message"]) - } - - - # get project board status - req_board_infos <- glue( - 'query{ - node(id: "$_project_id_$") { - ... on ProjectV2 { - items(first: 100) { - nodes{ - id - fieldValues(first: 100) { - nodes{ - ... on ProjectV2ItemFieldSingleSelectValue { - name - field { - ... on ProjectV2FieldCommon { - name - } - } - } - } - } - content{ - ...on Issue { - title - url - number - state - createdAt - updatedAt - closedAt - } - } - } - } - } - } - }', - .open = "$_", - .close = "_$" - ) - - board_infos <- gh_gql( - req_board_infos, - .token = github_token - ) - - board_infos <- board_infos$data$node$items$nodes - - - # export to tibble - board_tbl <- map_dfr(board_infos, function(issue) { - multilist_to_tibble(issue) %>% - unnest_wider(content) %>% - mutate(board_column = unlist(fieldValues[[1]])[1]) %>% - select(-fieldValues) - }) - - return(board_tbl) -} diff --git a/R/keyring.R b/R/keyring.R new file mode 100644 index 0000000..e8b1b51 --- /dev/null +++ b/R/keyring.R @@ -0,0 +1,104 @@ +init_keyring <- function( + keyring_name = getOption("noclocks.keyring.name", default = "noclocks"), + username = getOption("noclocks.keyring.username", default = "noclocks"), + password = getOption("noclocks.keyring.password", default = NULL), + ... +) { + + stopifnot(keyring::has_keyring_support()) + + keyrings <- keyring::keyring_list()$keyring |> unique() + + if (keyring_name %in% keyrings) { + rlang::warn( + "Keyring already exists. Skipping creation." + ) + } else { + keyring::keyring_create( + keyring = keyring_name,, + password = password + ) + + msg <- glue::glue( + "Keyring '{name}' created successfully.", + "To add secrets to the keyring, use `noclocksR::add_secret()`." + ) + + rlang::inform(msg) + } + + if (getOption("noclocks.keyring.username", default = "noclocks") != username) { + msg <- glue::glue( + "Setting the default keyring username to '{username}':", + "To change this, use `options(noclocks.keyring.username = '')`." + ) + + options("noclocks.keyring.username" = username) + + rlang::inform(msg) + } + + if (!is.null(password)) { + msg <- glue::glue( + "Setting the default keyring password:", + "To change this, use `options(noclocks.keyring.password = '')`." + ) + + options("noclocks.keyring.password" = password) + + rlang::inform(msg) + } + + invisible(TRUE) + +} + +check_keyring <- function( + keyring = "noclocks" +) { + + stopifnot(keyring::has_keyring_support()) + + keyrings <- keyring::keyring_list()$keyring |> unique() + + if (keyring %in% keyrings) { + rlang::inform( + glue::glue( + "Keyring '{keyring}' exists." + ) + ) + } else { + rlang::abort( + glue::glue( + "Keyring '{keyring}' does not exist.", + "Please create it using `noclocksR::init_keyring()`." + ) + ) + } + + invisible(TRUE) + +} + +get_secret <- function( + secret, + username = "noclocks", + keyring = "noclocks", + ... +) { + + stopifnot(check_keyring(keyring)) + + secret <- keyring::key_get( + service = secret, + keyring = keyring, + password = password + ) + + secret +} + +setup_keyring <- function() { + keyring::keyring_path <- here::here("keyring") + keyring::keyring_path +} diff --git a/R/onLoad.R b/R/onLoad.R new file mode 100644 index 0000000..8975dcd --- /dev/null +++ b/R/onLoad.R @@ -0,0 +1,55 @@ +#' Startup Functions +#' +#' @description +#' These functions are run when the package is loaded or attached. + +.onAttach <- function( + libname = find.package("noclocksR"), + pkgname = "noclocksR" +) { + + vers <- as.character(utils::packageVersion(pkgname)) + msg <- sprintf( + "Welcome to `noclocksR`! This is version: %s\n", + vers + ) + + if (interactive()) { + packageStartupMessage(msg) + } + + # force the use of HTTP/2 + httr::set_config(httr::config(http_version = 2)) + +} + +#' @importFrom gargle gargle_oauth_client_from_json init_AuthState +#' @importFrom fs path_package +#' @importFrom pkgload pkg_name +#' @importFrom utils assignInMyNamespace +.onLoad <- function(libname, pkgname) { + + # oauth_client <- gargle::gargle_oauth_client_from_json( + # path = fs::path_package( + # pkgload::pkg_name(), + # "inst/config/noclocksr-oauth-client.json" + # ) + # ) + # + # .auth_env <<- rlang::env( + # auth_state = gargle::init_AuthState( + # package = "noclocksR", + # client = oauth_client, + # auth_active = TRUE + # ) + # ) + +} + +# .auth_env <- NULL +# Load the auth state +# if (!exists(".auth_env", envir = .GlobalEnv)) { +# source("onLoad.R") +# } + + diff --git a/R/pkgdown.R b/R/pkgdown.R index 708ca85..6d42e8e 100644 --- a/R/pkgdown.R +++ b/R/pkgdown.R @@ -1,3 +1,9 @@ +# use_noclocks_pkgdown <- function( +# pkg = pkgload::pkg_name(), +# pkgdown_path = "public", +# +# ) + #' No Clocks `pkgdown` #' #' @description @@ -5,9 +11,9 @@ #' extra reports. #' #' The reports that can be included are: -#' - Test Results Report via [testdown::testdown()] +#' - Test Results Report via [testdown::test_down()] #' - Test Coverage Results via [covrpage::covrpage()] -#' - Git Reports via [gitdown::gitdown()] +#' - Git Reports via [gitdown::git_down()] #' #' @param pkg (character) Path to the package in development #' @param pkgdown_path (character) Relative path inside the package to store @@ -23,6 +29,15 @@ #' #' @importFrom fs dir_create file_move #' @importFrom pkgdown build_site +#' @importFrom covr package_coverage report +#' @importFrom covrpage covrpage +#' @importFrom testdown test_down +#' @importFrom gitdown git_down +#' @importFrom markdown markdownToHTML +#' @importFrom jsonlite fromJSON +#' @importFrom glue glue +#' @import htmltools +#' @import pkgdown #' #' @return None Generate a pkgdown with test and coverage reports #' @export @@ -35,7 +50,7 @@ #' # reports = c("testdown","coverage") #' } build_pkgdown_with_reports <- function( - pkg = ".", + pkg = getwd(), pkgdown_path = "public", assets_path = "pkgdown/assets", reports = c("coverage", "testdown", "gitdown"), @@ -68,20 +83,23 @@ build_pkgdown_with_reports <- function( } if (!requireNamespace("DT", quietly = TRUE)) { stop( - "{DT} needs to be installed for" + "{DT} needs to be installed" ) } if (!requireNamespace("htmltools", quietly = TRUE)) { stop( - "{htmltools} needs to be installed for" + "{htmltools} needs to be installed" ) } if (!requireNamespace("markdown", quietly = TRUE)) { stop( - "{markdown} needs to be installed for" + "{markdown} needs to be installed" ) } - covr_pkg <- covr::package_coverage(path = pkg) + covr_pkg <- covr::package_coverage( + path = pkg, + install_path = file.path(pkg, "covr") + ) covr::report( x = covr_pkg, file = file.path(assets_path, "coverage", "coverage.html"), @@ -108,7 +126,7 @@ build_pkgdown_with_reports <- function( if (isTRUE("testdown" %in% reports)) { if (!requireNamespace("testdown", quietly = TRUE)) { stop( - "{testdown} needs to be installed for" + "{testdown} needs to be installed" ) } @@ -124,7 +142,7 @@ build_pkgdown_with_reports <- function( if (isTRUE("gitdown" %in% reports)) { if (!requireNamespace("gitdown", quietly = TRUE)) { stop( - "{gitdown} needs to be installed for" + "{gitdown} needs to be installed" ) } @@ -167,6 +185,10 @@ build_pkgdown_with_reports <- function( pkgdown::build_site( pkg = pkg, override = yaml_settings, - preview = FALSE + preview = FALSE, + devel = TRUE, + install = FALSE, + new_process = TRUE ) } + diff --git a/R/pkgenv.R b/R/pkgenv.R new file mode 100644 index 0000000..13d9d0a --- /dev/null +++ b/R/pkgenv.R @@ -0,0 +1,23 @@ +#' @keywords internal +#' @noRd +.pkgenv <- new.env(parent = emptyenv()) +.pkgenv$configs <- list() +.pkgenv$secrets <- list() +.pkgenv$completions <- list() +.pkgenv$paths <- list() +.pkgenv$auth <- NULL + +#' @keywords internal +#' @noRd +find_pkgenv <- function() { + names( + which( + sapply( + loadedNamespaces(), + function(x) { + any(grepl("^.pkgenv$", ls(envir = asNamespace(x)))) + } + ) + ) + ) +} diff --git a/R/plumber.R b/R/plumber.R new file mode 100644 index 0000000..e69de29 diff --git a/R/resend.R b/R/resend.R new file mode 100644 index 0000000..9266348 --- /dev/null +++ b/R/resend.R @@ -0,0 +1,4 @@ +#' Resend Email Service Functions +#' +#' @description +#' diff --git a/R/rstudio_addins.R b/R/rstudio_addins.R new file mode 100644 index 0000000..63165c1 --- /dev/null +++ b/R/rstudio_addins.R @@ -0,0 +1,47 @@ +# rs_shiny_mod <- function( +# name = NULL, +# open = rlang::is_interactive() +# ) { +# +# rfile <- glue::glue("mod_{name}.R") +# rfilepath <- usethis::proj_path("R", rfile) +# +# if (!fs::file_exists(rfilepath)) { +# fs::file_create(rfilepath) +# +# roxy <- glue::glue( +# .sep = "\n", +# "#' {stringr::str_to_title(name)} Shiny Module", +# "#'", +# "#' @description", +# "#' {stringr::str_to_title(name)} Shiny Module", +# "#'", +# "#' @param id The module id to use for namespacing", +# "#' @param input,output,session The shiny server function arguments", +# "#' @param ... Additional arguments", +# "#'", +# "#' @return The UI module returns an [htmltools::tagList()];", +# "#' the server module returns a [shiny::reactive()]", +# "#'", +# "#' @export", +# "#'", +# "#' @importFrom shiny moduleServer NS reactive observe", +# "#' @importFrom htmltools tags tagList" +# ) +# +# ui_func <- glue::glue( +# .sep = "\n", +# "#' {name} Module", +# "mod_{name}_ui <- function(id, ...) {", +# " ns <- shiny::NS(id)", +# " htmltools::tagList(", +# " ", +# " )", +# "}", +# "", +# +# +# )", +# ) +# +# } diff --git a/R/shiny_meta.R b/R/shiny_meta.R new file mode 100644 index 0000000..af35d99 --- /dev/null +++ b/R/shiny_meta.R @@ -0,0 +1,88 @@ +#' Use No Clocks `` +#' +#' @description +#' Adds `` tags and social media cards for No Clocks, LLC. +#' +#' @param name (character) The name of the site +#' @param version (character) The version of the site +#' @param description (character) The description of the site +#' @param url (character) The URL of the site +#' @param theme_color (character) The theme color of the site +#' @param robots (character) The robots meta tag +#' @param generator (character) The generator meta tag +#' @param subject (character) The subject meta tag +#' @param rating (character) The rating meta tag +#' @param referrer (character) The referrer meta tag +#' @param csp (character) The content security policy meta tag +#' @param image (character) The image URL for the site +#' @param image_alt (character) The image alt text for the site +#' @param twitter_creator (character) The Twitter creator meta tag +#' @param twitter_card_type (character) The Twitter card type meta tag +#' @param twitter_site (character) The Twitter site meta tag +#' @param ... Additional arguments +#' +#' @seealso [metathis::meta()] +#' +#' @return HTML via [htmltools::tags()] and `` tags via +#' [metathis::meta()] +#' @export +#' +#' @example examples/ex_use_noclocks_meta.R +#' +#' @importFrom metathis meta meta_social +use_noclocks_meta <- function( + name = "noclocks", + version = "0.0.1", + description = " and social media cards for No Clocks, LLC", + url = "https://noclocks.dev", + theme_color = "#000000", + robots = "index,follow", + generator = "R-Shiny", + subject = "No Clocks, LLC", + rating = "General", + referrer = "origin", + csp = "default-src 'self'", + image = noclocks_logo(url = TRUE), + image_alt = "No Clocks, LLC Logo", + twitter_creator = "@noclocksdev", + twitter_card_type = "summary_large_image", + twitter_site = "@noclocksdev", + ... +) { + + # noclocks_brand()$colors |> dplyr::filter(type == "dark") |> dplyr::pull(hex) + + htmltools::tags$head( + metathis::meta() |> + metathis::meta_viewport(maximum_scale = 1) |> + metathis::meta_general( + application_name = name, + theme_color = theme_color, + description = description, + robots = robots, + generator = generator, + subject = subject, + rating = rating, + referrer = referrer + ) |> + metathis::meta_tag( + "http-equiv" = "Content-Security-Policy", + "content" = csp + ) |> + metathis::meta_name( + "package" = name, + "version" = version + ) |> + metathis::meta_social( + title = name, + description = description, + url = url, + image = image, + image_alt = image_alt, + twitter_creator = twitter_creator, + twitter_card_type = twitter_card_type, + twitter_site = twitter_site + ) + ) + +} diff --git a/R/slack.R b/R/slack.R new file mode 100644 index 0000000..e69de29 diff --git a/R/supabase.R b/R/supabase.R new file mode 100644 index 0000000..e69de29 diff --git a/R/theming.R b/R/theming.R new file mode 100644 index 0000000..157aa9c --- /dev/null +++ b/R/theming.R @@ -0,0 +1,80 @@ +#' No Clocks Theming +#' +#' @description +#' These functions contain various common theming elements and design assets +#' use at No Clocks, LLC across Shiny applications, Quarto and R Markdown +#' documents, and other R related projects. +#' +#' @name theming +NULL + +#' No Clocks Color Palette +#' +#' @description +#' The No Clocks color palette is a set of colors that are used in various +#' No Clocks, LLC projects. The palette is designed to be visually appealing +#' and accessible. +#' +#' @seealso [No Clocks Colors]() +#' @seealso [No Clocks Brand Guidelines - Colors]() +#' +#' @return A named vector of colors +#' +#' @export +#' +#' @example examples/ex_color_palette.R +color_palette <- function() { + c( + "primary" = "#007bff", + "secondary" = "#6c757d", + "success" = "#28a745", + "info" = "#17a2b8", + "warning" = "#ffc107", + "danger" = "#dc3545", + "light" = "#f8f9fa", + "dark" = "#343a40" + ) +} + +#' No Clocks Typography (Fonts) +#' +#' @description +#' The No Clocks typography is a set of fonts that are used in various +#' No Clocks, LLC projects. The typography is designed to be visually appealing +#' and accessible. +#' +#' @seealso [No Clocks Typography]() +#' +#' @return A named vector of fonts +#' +#' @export +#' +#' @example examples/ex_typography.R +typography <- function() { + c( + "primary" = "Roboto", + "secondary" = "Open Sans", + "tertiary" = "Lato" + ) +} + +# license <- htmltools::HTML( +# '
+#

Licensing

+#

Except where otherwise noted, content on this app is licensed under CC BY-NC-SA 4.0.

+# +# Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License +# +# +# +# +# +# +# +# +# +# +# +# +#
' +# ) diff --git a/R/utils.R b/R/utils.R index c5d3a3c..2e9134a 100644 --- a/R/utils.R +++ b/R/utils.R @@ -1 +1,12 @@ `%||%` <- function (x, y) { if (is.null(x)) y else x } + +is_windows <- function() { + tolower( + Sys.info()[["sysname"]] + ) == "windows" +} + +is.oauth_app <- function(x) { + inherits(x, "oauth_app") +} + diff --git a/R/utils_branding.R b/R/utils_branding.R new file mode 100644 index 0000000..222df6f --- /dev/null +++ b/R/utils_branding.R @@ -0,0 +1,319 @@ + +# ------------------------------------------------------------------------ +# +# Title : Branding Utilities +# By : Jimmy Briggs +# Date : 2024-06-17 +# +# ------------------------------------------------------------------------ + + +#' Fetch a Brand using the Brandfetch API +#' +#' @description +#' This function fetches a brand using the +#' [Brandfetch Brand API](https://docs.brandfetch.com/reference/brand-api). +#' +#' @param domain The domain of the brand to fetch +#' @param brandfetch_api_key The API key for the Brandfetch API +#' @param ... Additional arguments +#' +#' @return A tibble with the brand information +#' +#' @export +#' +#' @example examples/ex_brandfetch.R +#' +#' @importFrom httr2 request req_url_path_append req_method req_auth_bearer_token req_headers req_perform +#' @importFrom tibblify tibblify tspec_object tib_chr tib_lgl tib_dbl tib_unspecified tib_df tib_row +#' @importFrom purrr pluck +#' @importFrom tidyr unnest +#' @importFrom rlang abort +#' @importFrom config get +#' @importFrom tibble tibble +#' @importFrom httr2 resp_body_json +#' @importFrom dplyr pull +fetch_brand <- function( + domain, + brandfetch_api_key = Sys.getenv("BRANDFETCH_API_KEY", unset = config::get("brandfetch_api_key")), + ... +) { + + base_url <- "https://api.brandfetch.io/v2/brands" + + if (is.null(brandfetch_api_key)) { + brandfetch_api_key <- config::get("brandfetch_api_key") + } + + if (is.null(brandfetch_api_key)) { + rlang::abort("No Brandfetch API key found") + } + + req <- httr2::request( + base_url = base_url + ) |> + httr2::req_url_path_append( + domain + ) |> + httr2::req_method("GET") |> + httr2::req_auth_bearer_token(brandfetch_api_key) |> + httr2::req_headers( + `Accept` = "application/json", + `Content-Type` = "application/json" + ) + + res <- req |> httr2::req_perform() + if (res$status_code != 200) { + rlang::abort("Brandfetch API request failed") + } + + content <- res |> + httr2::resp_body_json() + + spec <- tibblify::tspec_object( + tibblify::tib_chr("id", required = FALSE), + tibblify::tib_chr("name", required = FALSE), + tibblify::tib_chr("domain", required = FALSE), + tibblify::tib_lgl("claimed", required = FALSE), + tibblify::tib_chr("description", required = FALSE), + tibblify::tib_chr("longDescription", required = FALSE), + tibblify::tib_dbl("qualityScore", required = FALSE), + tibblify::tib_unspecified("images"), + + tibblify::tib_df( + "links", + tibblify::tib_chr("name", required = FALSE), + tibblify::tib_chr("url", required = FALSE) + ), + + tibblify::tib_df( + "logos", + tibblify::tib_chr("theme", required = FALSE), + tibblify::tib_df( + "formats", + tibblify::tib_chr("src", required = FALSE), + tibblify::tib_unspecified("background", required = FALSE), + tibblify::tib_chr("format", required = FALSE), + tibblify::tib_int("height", required = FALSE), + tibblify::tib_int("width", required = FALSE), + tibblify::tib_int("size", required = FALSE), + ), + tibblify::tib_unspecified("tags", required = FALSE), + tibblify::tib_chr("type", required = FALSE) + ), + + tibblify::tib_df( + "colors", + tibblify::tib_chr("hex", required = FALSE), + tibblify::tib_chr("type", required = FALSE), + tibblify::tib_int("brightness", required = FALSE), + ), + + tibblify::tib_df( + "fonts", + tibblify::tib_chr("name", required = FALSE), + tibblify::tib_chr("type", required = FALSE), + tibblify::tib_chr("origin", required = FALSE), + tibblify::tib_chr("originId", required = FALSE), + tibblify::tib_unspecified("weights", required = FALSE), + ), + + tibblify::tib_row( + "company", + tibblify::tib_unspecified("employees"), + tibblify::tib_unspecified("foundedYear"), + tibblify::tib_unspecified("kind"), + tibblify::tib_unspecified("location"), + tibblify::tib_df( + "industries", + tibblify::tib_unspecified("id", required = FALSE), + tibblify::tib_unspecified("parent"), + tibblify::tib_dbl("score", required = FALSE), + tibblify::tib_chr("name", required = FALSE), + tibblify::tib_chr("emoji", required = FALSE), + tibblify::tib_chr("slug", required = FALSE) + ) + ) + ) + + out <- tibblify::tibblify(content, spec, unspecified = "drop") + out$logos <- out$logos |> tidyr::unnest("formats") + out$company <- out$company |> purrr::pluck("industries") + + return(out) + +} + + +get_brand_logos <- function( + brand, + path, + ... +) { + + brand_logos <- brand$logos |> + dplyr::mutate( + file = purrr::pmap_chr( + list( + brand_name = brand$name, + type = type, + format = format, + height = height, + width = width + ), + get_logo_file_name + ) + ) + + purrr::walk2( + brand_logos$src, + brand_logos$file, + ~download_logo( + src = .x, + file = .y, + name = brand$name, + type = brand_logos$type, + format = brand_logos$format, + height = brand_logos$height, + width = brand_logos$width + ) + ) + + return( + invisible(TRUE) + ) + + + + +} + +#' Download Brand Logo File +#' +#' @description +#' This function downloads a brand logo file from a URL to the specified path. +#' +#' @param src The URL of the logo file +#' @param file The name of the logo file +#' @param name The name of the brand +#' @param path The path to save the logo file +#' @param type The type of logo (icon or logo) +#' @param format The format of the logo (png, svg, jpeg) +#' @param height The height of the logo +#' @param width The width of the logo +#' @param ... Additional arguments +#' +#' @return Invisible +#' @export +#' +#' @importFrom stringr str_replace_all str_to_lower +#' @importFrom fs dir_exists dir_create path +download_logo <- function( + src, + file, + name, + path = "inst/extdata/brand", + type = c("icon", "logo"), + format = c("png", "svg", "jpeg"), + height, + width, + ... +) { + + type <- match.arg(type) + format <- match.arg(format) + height <- as.integer(height) + width <- as.integer(width) + src <- src |> stringr::str_replace_all(" ", "%20") + brand_name_clean <- stringr::str_to_lower(name) |> stringr::str_replace_all(" ", "_") + size <- paste0(as.character(height), "x", as.character(width)) + + if (!fs::dir_exists(path)) { + fs::dir_create(path) + } + + file_path <- fs::path(path, file) + + download.file( + src, + destfile = file_path, + method = "curl" + ) + + return( + invisible(TRUE) + ) + +} + +#' @param brand_name The name of the brand +#' @param type The type of logo +#' @param format The format of the logo +#' @param height The height of the logo +#' @param width The width of the logo +#' @param ... Additional arguments +#' +#' @return The file name of the logo +#' +#' @export +#' +#' @noRd +#' +#' @keywords internal +#' +#' @importFrom stringr str_to_lower str_replace_all +#' @importFrom purrr pmap_chr +#' @importFrom dplyr mutate +#' @importFrom fs dir_exists dir_create path +get_logo_file_name <- function( + brand_name, + type, + format, + height, + width, + ... +) { + + brand_name_clean <- stringr::str_to_lower(brand_name) |> stringr::str_replace_all(" ", "_") + size <- "" + if (!is.na(height) && !is.na(width) && format != "svg") { + size <- paste0("-", as.character(height), "x", as.character(width)) + } + + paste0( + brand_name_clean, + "-", + type, + size, + ".", + format + ) + +} + +# brand_logos <- brand$logos |> +# dplyr::mutate( +# file = purrr::pmap_chr( +# list( +# brand_name = brand$name, +# type = type, +# format = format, +# height = height, +# width = width +# ), +# get_logo_file_name +# ) +# ) + + + + +# download_logo( +# src = brand_logos$src[3], +# file = brand_logos$file[3], +# name = brand$name, +# type = brand_logos$type[3], +# format = brand_logos$format[3], +# height = brand_logos$height[3], +# width = brand_logos$width[3] +# ) diff --git a/R/wrappers.R b/R/wrappers.R new file mode 100644 index 0000000..1ccd789 --- /dev/null +++ b/R/wrappers.R @@ -0,0 +1,35 @@ +#' @importFrom digest digest +#' @export +digest::digest + +#' @importFrom parallel detectCores +#' @export +parallel::detectCores + +#' @importFrom cli ansi_strip +#' @export +cli::ansi_strip + +#' @importFrom cli tree +#' @export +cli::tree + +#' @importFrom rlang entrace +#' @export +rlang::entrace + +#' @importFrom rlang cnd_entrace +#' @export +rlang::cnd_entrace + +#' @importFrom rlang abort +#' @export +rlang::abort + +#' @importFrom rlang error_cnd +#' @export +rlang::error_cnd + +#' @importFrom rlang trace_back +#' @export +rlang::trace_back diff --git a/_pkgdown.yml b/_pkgdown.yml index d8796c9..a0117e0 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -1,4 +1,23 @@ -url: https://noclocks.github.io/noclocksR/ +url: https://docs.noclocks.dev/noclocksR/ +home: + title: No Clocks Internal R Package +authors: + Jimmy Briggs: + href: https://github.com/jimbrig + Patrick Howard: + href: https://github.com/phoward38 + No Clocks, LLC: + href: https://github.com/noclocks template: bootstrap: 5 + light-switch: yes + bslib: + primary: '#121618' + border-radius: 0.5rem + btn-border-radius: 0.25rem + danger: '#A6081A' + includes: + in_header: +development: + mode: auto diff --git a/data-raw/R/utils_data_docs.R b/data-raw/R/utils_data_docs.R new file mode 100644 index 0000000..56bea4c --- /dev/null +++ b/data-raw/R/utils_data_docs.R @@ -0,0 +1,76 @@ +document_dataset <- function( + data_obj, + name = "DATASET_NAME", + description = "DATASET_DESCRIPTION", + source = "DATASET_SOURCE", + col_types = purrr::map_chr(data_obj, typeof), + col_descs = rep("COLUMN_DESCRIPTION", length(names(data_obj))), + file = fs::path("R", "data.R"), + append = TRUE, + overwrite = FALSE, + ... +) { + + col_names <- names(data_obj) + + if (missing(col_types) || is.null(col_types)) { + col_types <- purrr::map_chr(data_obj, typeof) + } + + if (missing(col_descs) || is.null(col_descs)) { + col_descs <- rep("COLUMN_DESCRIPTION", length(col_names)) + } + + stopifnot( + length(col_types) == length(col_names), + length(col_descs) == length(col_names) + ) + + col_roxys <- glue::glue( + .open = "[[", + .close = "]]", + "#' \\item{\\code{[[col_names]]}}{[[col_types]]. [[col_descs]].}" + ) |> + paste(collapse = "\n") + + # cat(col_roxys) + + dims <- paste0( + nrow(data_obj), + " rows and ", + ncol(data_obj), + " columns" + ) + + pre <- glue::glue( + .sep = "\n", + "#' {name}", + "#'", + "#' @description", + "#' {description}", + "#'", + "#' @source", + "#' {source}", + "#'", + "#' @format A data.frame with {dims}:" + ) + + skeleton <- paste0( + pre, + "\n", + "#' \\describe{\n", + col_roxys, + "\n", + "#'}\n", + '"', name, '"\n' + ) + + cat(skeleton, + file = file, + append = append, + sep = "\n") + +} + + + diff --git a/data-raw/brand.R b/data-raw/brand.R new file mode 100644 index 0000000..e69de29 diff --git a/data-raw/color_palette.R b/data-raw/color_palette.R index e4b6759..22b3198 100644 --- a/data-raw/color_palette.R +++ b/data-raw/color_palette.R @@ -7,6 +7,25 @@ # # ------------------------------------------------------------------------ +c( + "AndreaCirilloAC/paletter", + "simmwill/coolors" +) |> purrr::walk( + pak::pak +) + +library(paletter) +library(RColorBrewer) +library(colorspace) +library(sass) +library(thematic) + +bootstrap_colors <- fresh::bs_vars_color( + +) + +paletter::optimize_palette() + styles <- list( "primary" = "#121618", "secondary_1" = "#E6AA68", diff --git a/data-raw/contacts.R b/data-raw/contacts.R new file mode 100644 index 0000000..7d744b1 --- /dev/null +++ b/data-raw/contacts.R @@ -0,0 +1,11 @@ + +# ------------------------------------------------------------------------ +# +# Title : No Clocks, LLC Contacts Data Preparation +# By : Jimmy Briggs +# Date : 2024-06-17 +# +# ------------------------------------------------------------------------ + +require(httr2) +require(gargle) diff --git a/data-raw/http_status_codes.R b/data-raw/http_status_codes.R new file mode 100644 index 0000000..22d960c --- /dev/null +++ b/data-raw/http_status_codes.R @@ -0,0 +1,102 @@ + +# ------------------------------------------------------------------------ +# +# Title : HTTP Status Codes Dataset +# By : Jimmy Briggs +# Date : 2024-06-19 +# +# ------------------------------------------------------------------------ + +require(tibble) + + +# status codes ----------------------------------------------------------- + +http_status_codes <- tibble::tribble( + ~code, ~message, ~category, + 100L, "Continue", "Informational", + 101L, "Switching Protocols", "Informational", + 102L, "Processing", "Informational", + 103L, "Early Hints", "Informational", + 200L, "OK", "Success", + 201L, "Created", "Success", + 202L, "Accepted", "Success", + 203L, "Non-Authoritative Information", "Success", + 204L, "No Content", "Success", + 205L, "Reset Content", "Success", + 206L, "Partial Content", "Success", + 207L, "Multi-Status", "Success", + 208L, "Already Reported", "Success", + 226L, "IM Used", "Success", + 300L, "Multiple Choices", "Redirection", + 301L, "Moved Permanently", "Redirection", + 302L, "Found", "Redirection", + 303L, "See Other", "Redirection", + 304L, "Not Modified", "Redirection", + 305L, "Use Proxy", "Redirection", + 306L, "(Unused)", "Redirection", + 307L, "Temporary Redirect", "Redirection", + 308L, "Permanent Redirect", "Redirection", + 400L, "Bad Request", "Client Error", + 401L, "Unauthorized", "Client Error", + 402L, "Payment Required", "Client Error", + 403L, "Forbidden", "Client Error", + 404L, "Not Found", "Client Error", + 405L, "Method Not Allowed", "Client Error", + 406L, "Not Acceptable", "Client Error", + 407L, "Proxy Authentication Required", "Client Error", + 408L, "Request Timeout", "Client Error", + 409L, "Conflict", "Client Error", + 410L, "Gone", "Client Error", + 411L, "Length Required", "Client Error", + 412L, "Precondition Failed", "Client Error", + 413L, "Payload Too Large", "Client Error", + 414L, "URI Too Long", "Client Error", + 415L, "Unsupported Media Type", "Client Error", + 416L, "Range Not Satisfiable", "Client Error", + 417L, "Expectation Failed", "Client Error", + 418L, "I'm a teapot", "Client Error", + 421L, "Misdirected Request", "Client Error", + 422L, "Unprocessable Entity", "Client Error", + 423L, "Locked", "Client Error", + 424L, "Failed Dependency", "Client Error", + 425L, "Too Early", "Client Error", + 426L, "Upgrade Required", "Client Error", + 428L, "Precondition Required", "Client Error", + 429L, "Too Many Requests", "Client Error", + 431L, "Request Header Fields Too Large", "Client Error", + 451L, "Unavailable For Legal Reasons", "Client Error", + 500L, "Internal Server Error", "Server Error", + 501L, "Not Implemented", "Server Error", + 502L, "Bad Gateway", "Server Error", + 503L, "Service Unavailable", "Server Error", + 504L, "Gateway Timeout", "Server Error", + 505L, "HTTP Version Not Supported", "Server Error", + 506L, "Variant Also Negotiates", "Server Error", + 507L, "Insufficient Storage", "Server Error", + 508L, "Loop Detected", "Server Error", + 510L, "Not Extended", "Server Error", + 511L, "Network Authentication Required", "Server Error" +) + +usethis::use_data(http_status_codes, overwrite = TRUE) + + +# document dataset -------------------------------------------------------- + +source(fs::path("data-raw/R/utils_data_docs.R")) + +col_descs <- c( + "The HTTP Status Code", + "The HTTP Status Message", + "The HTTP Status Category" +) + +document_dataset( + http_status_codes, + name = "http_status_codes", + description = "A dataset containing HTTP status codes and their corresponding messages.", + source = "https://en.wikipedia.org/wiki/List_of_HTTP_status_codes", + col_descs = col_descs +) + diff --git a/data-raw/internal.R b/data-raw/internal.R new file mode 100644 index 0000000..0e26cb9 --- /dev/null +++ b/data-raw/internal.R @@ -0,0 +1,37 @@ + +# ------------------------------------------------------------------------ +# +# Title : No Clocks Internal Datasets +# By : Jimmy Briggs +# Date : 2024-06-19 +# +# ------------------------------------------------------------------------ + + + + +# urls -------------------------------------------------------------------- + + + +# github ------------------------------------------------------------------ + + + +# configurations ---------------------------------------------------------- + + + +# databases --------------------------------------------------------------- + + + +# google sheets ----------------------------------------------------------- + +noclocks_gsheet_clients_id <- "" +noclocks_ghseet_timelogs_id <- "" + + +# slack channels ---------------------------------------------------------- + + diff --git a/data/http_status_codes.rda b/data/http_status_codes.rda new file mode 100644 index 0000000..1180743 Binary files /dev/null and b/data/http_status_codes.rda differ diff --git a/dev/ROADMAP.md b/dev/ROADMAP.md new file mode 100644 index 0000000..00c3970 --- /dev/null +++ b/dev/ROADMAP.md @@ -0,0 +1,31 @@ +# Roadmap + +## Features + +### Favicon + +- `use_favicon()` +- `create_favicon_package()` +- `favicon()` +- `build_favicon()` + +### Metadata + +- `use_meta()` + +### Theme + +- `app_theme_bslib()` +- `app_theme_fresh()` +- `create_theme()` +- `use_theme()` + +### Layout + +### Loading Spinners + +- `use_spinner()` + +### Navigation + +- `use_navbar()` diff --git a/dev/config_attachment.yaml b/dev/config_attachment.yaml new file mode 100644 index 0000000..46e24ec --- /dev/null +++ b/dev/config_attachment.yaml @@ -0,0 +1,12 @@ +path.n: NAMESPACE +path.d: DESCRIPTION +dir.r: R +dir.v: vignettes +dir.t: tests +extra.suggests: ~ +pkg_ignore: ~ +document: yes +normalize: yes +inside_rmd: no +must.exist: yes +check_if_suggests_is_installed: yes diff --git a/dev/data.R b/dev/data.R new file mode 100644 index 0000000..e69de29 diff --git a/dev/functions.R b/dev/functions.R new file mode 100644 index 0000000..53ab135 --- /dev/null +++ b/dev/functions.R @@ -0,0 +1,3 @@ +new_function <- function( + +) diff --git a/dev/pkgcheck.R b/dev/pkgcheck.R new file mode 100644 index 0000000..3674276 --- /dev/null +++ b/dev/pkgcheck.R @@ -0,0 +1,22 @@ +check_pkg_ns <- function(pkg, quiet = FALSE) { + if (isFALSE(quiet)) { + # with messages + if (!isNamespaceLoaded(pkg)) { + if (requireNamespace(pkg, quietly = FALSE)) { + cat(paste0("Loading package: ", pkg, "\n")) + } else { + stop(paste0(pkg, " not available")) + } + } else { + cat(paste0("Package ", pkg, " loaded\n")) + } + } else { + # without messages + if (!isNamespaceLoaded(pkg)) { + if (requireNamespace(pkg, quietly = TRUE)) { + } else { + stop(paste0(pkg, " not available")) + } + } + } +} diff --git a/dev/pkgdevt.R b/dev/pkgdevt.R index 9f93f49..7fc43b8 100644 --- a/dev/pkgdevt.R +++ b/dev/pkgdevt.R @@ -110,6 +110,7 @@ usethis::use_rmarkdown_template( usethis::use_vignette("noclocksR") usethis::use_vignette("styleguide") +usethis::use_vignette("shiny") usethis::use_pkgdown() usethis::use_pkgdown_github_pages() diff --git a/dev/tools/deployment.R b/dev/tools/deployment.R new file mode 100644 index 0000000..e69de29 diff --git a/dev/vignettes.R b/dev/vignettes.R new file mode 100644 index 0000000..711564a --- /dev/null +++ b/dev/vignettes.R @@ -0,0 +1,32 @@ + +# ------------------------------------------------------------------------ +# +# Title : noclocksR Package Vignettes +# By : Jimmy Briggs +# Date : 2024-06-16 +# +# ------------------------------------------------------------------------ + +require(usethis) +require(devtools) +require(knitr) +require(markdown) +require(rmarkdown) + + +# vignettes --------------------------------------------------------------- + +c( + "noclocksR", + "devenv", + "pkgdevt", + "shiny", + "plumber", + "about_branding", + "about_theming", + "about_colors", + "integrations" +) |> + purrr::walk( + usethis::use_vignette + ) diff --git a/examples/ex_brandfetch.R b/examples/ex_brandfetch.R new file mode 100644 index 0000000..e69de29 diff --git a/examples/ex_color_palette.R b/examples/ex_color_palette.R new file mode 100644 index 0000000..e69de29 diff --git a/examples/ex-shiny_resume.R b/examples/ex_shiny_resume.R similarity index 100% rename from examples/ex-shiny_resume.R rename to examples/ex_shiny_resume.R diff --git a/examples/ex_typography.R b/examples/ex_typography.R new file mode 100644 index 0000000..e69de29 diff --git a/examples/ex_use_noclocks_meta.R b/examples/ex_use_noclocks_meta.R new file mode 100644 index 0000000..a9696eb --- /dev/null +++ b/examples/ex_use_noclocks_meta.R @@ -0,0 +1,7 @@ +if (FALSE) { + + shiny::fluidPage( + use_noclocks_meta() + ) + +} diff --git a/inst/assets/README.md b/inst/assets/README.md new file mode 100644 index 0000000..e69de29 diff --git a/inst/assets/components/button.html b/inst/assets/components/button.html new file mode 100644 index 0000000..484a85d --- /dev/null +++ b/inst/assets/components/button.html @@ -0,0 +1 @@ + diff --git a/inst/assets/components/checkbox.html b/inst/assets/components/checkbox.html new file mode 100644 index 0000000..e69de29 diff --git a/inst/assets/favicons/gmh/favicon.ico b/inst/assets/favicons/gmh/favicon.ico new file mode 100644 index 0000000..5390cb5 Binary files /dev/null and b/inst/assets/favicons/gmh/favicon.ico differ diff --git a/inst/assets/images/Favicon-32x32.jpeg b/inst/assets/img/Favicon-32x32.jpeg similarity index 100% rename from inst/assets/images/Favicon-32x32.jpeg rename to inst/assets/img/Favicon-32x32.jpeg diff --git a/inst/assets/images/Favicon-32x32.png b/inst/assets/img/Favicon-32x32.png similarity index 100% rename from inst/assets/images/Favicon-32x32.png rename to inst/assets/img/Favicon-32x32.png diff --git a/inst/assets/images/Favicon.png b/inst/assets/img/Favicon.png similarity index 100% rename from inst/assets/images/Favicon.png rename to inst/assets/img/Favicon.png diff --git a/inst/assets/images/main-logo-black-transparent.png b/inst/assets/img/main-logo-black-transparent.png similarity index 100% rename from inst/assets/images/main-logo-black-transparent.png rename to inst/assets/img/main-logo-black-transparent.png diff --git a/inst/assets/images/main-logo-black.jpeg b/inst/assets/img/main-logo-black.jpeg similarity index 100% rename from inst/assets/images/main-logo-black.jpeg rename to inst/assets/img/main-logo-black.jpeg diff --git a/inst/assets/images/main-logo-black.jpg b/inst/assets/img/main-logo-black.jpg similarity index 100% rename from inst/assets/images/main-logo-black.jpg rename to inst/assets/img/main-logo-black.jpg diff --git a/inst/assets/images/main-logo-black.png b/inst/assets/img/main-logo-black.png similarity index 100% rename from inst/assets/images/main-logo-black.png rename to inst/assets/img/main-logo-black.png diff --git a/inst/assets/images/main-logo-transparent.png b/inst/assets/img/main-logo-transparent.png similarity index 100% rename from inst/assets/images/main-logo-transparent.png rename to inst/assets/img/main-logo-transparent.png diff --git a/inst/assets/images/main-logo-white-transparent.png b/inst/assets/img/main-logo-white-transparent.png similarity index 100% rename from inst/assets/images/main-logo-white-transparent.png rename to inst/assets/img/main-logo-white-transparent.png diff --git a/inst/assets/images/main-logo-white.jpeg b/inst/assets/img/main-logo-white.jpeg similarity index 100% rename from inst/assets/images/main-logo-white.jpeg rename to inst/assets/img/main-logo-white.jpeg diff --git a/inst/assets/images/main-logo-white.png b/inst/assets/img/main-logo-white.png similarity index 100% rename from inst/assets/images/main-logo-white.png rename to inst/assets/img/main-logo-white.png diff --git a/inst/assets/images/main-logo.jpeg b/inst/assets/img/main-logo.jpeg similarity index 100% rename from inst/assets/images/main-logo.jpeg rename to inst/assets/img/main-logo.jpeg diff --git a/inst/assets/images/main-logo.png b/inst/assets/img/main-logo.png similarity index 100% rename from inst/assets/images/main-logo.png rename to inst/assets/img/main-logo.png diff --git a/inst/assets/js/app.js b/inst/assets/js/app.js new file mode 100644 index 0000000..3ba4656 --- /dev/null +++ b/inst/assets/js/app.js @@ -0,0 +1,77 @@ +/** + * No Clocks, LLC JavaScript Library + * @see {@link https://shiny.rstudio.com/articles/js-events.html} + */ + $(function() { + + $('document').ready(function(){ + console.log('Ready!'); + }); + + // Extend shinydashboard::box + $('.box .btn-box-tool').on('click', function(e){ + // console.log(this.getAttribute('data-collapsed')); + }); + + // Add magnifying glass icon to expandable images and handle behaviors + $(document).on('shiny:value', function(event) { + event.target.addEventListener('DOMNodeInserted', function(e){ + if(typeof(e.target.querySelectorAll) != 'undefined'){ + let nodes = e.target.querySelectorAll('img.expandable:not([draggable])'); + if(nodes.length) { + Array.from(nodes).forEach(imageExpansion); + } + } + }); + }); + + // Typeset MathJax + Shiny.addCustomMessageHandler('typeset-mathjax', function(message) { + /** @deprecated will need to update if shiny adopts MathJax 3.x */ + if(window.MathJax) MathJax.Hub.Queue(["Typeset", MathJax.Hub]); + }); + + /** + * Shiny bindings + */ + /* + $(document).on('shiny:bound', function(event) { + if(event.bindingType == "input") { + // ... + } else if (event.bindingType == "output") { + // ... + } + }); + */ + + function imageExpansion(node) { + let parent = node.parentNode; + let button = document.createElement('button'); + button.innerHTML = ' '; + button.title = 'Click to Expand'; + button.setAttribute('aria-expanded', "false"); + button.classList.add("expand-img-btn"); + button.addEventListener('click', function(){ + node.classList.toggle('expanded'); + if(button.getAttribute('aria-expanded') === "true") { + button.setAttribute('aria-expanded', "false"); + } else { + button.setAttribute('aria-expanded', "true"); + button.focus(); + } + }); + node.draggable = false; + node.addEventListener('click', function() { + node.classList.remove('expanded'); + button.setAttribute('aria-expanded', "false"); + }); + parent.addEventListener('keydown', function(event) { + event = event || window.event; + if ('key' in event && (event.key === 'Escape' || event.key === 'Esc')) { + node.classList.remove('expanded'); + button.setAttribute('aria-expanded', "false"); + } + }); + parent.appendChild(button); + } + }) diff --git a/inst/assets/noclocks.png b/inst/assets/noclocks.png new file mode 100644 index 0000000..0bdf36c Binary files /dev/null and b/inst/assets/noclocks.png differ diff --git a/inst/assets/scss/_colors.scss b/inst/assets/scss/_colors.scss new file mode 100644 index 0000000..ec87d18 --- /dev/null +++ b/inst/assets/scss/_colors.scss @@ -0,0 +1,122 @@ +/* SASS Colors */ + +// No Clocks Default Colors + +$default-white: #ffffff; +$default-black: #000000; + +// Element Colors + +$body-bg-light: $noclocks-white; +$body-color-light: $noclocks-black; +$link-color-light: $noclocks-blue; + +$body-bg-dark: $noclocks-black; +$body-color-dark: $noclocks-white; +$link-color-dark: $noclocks-blue; + + +$body-color: $noclocks-black; +$link-color: $noclocks-blue; + + +// No Clocks Color Variables + +$noclocks-black: #000000; +$noclocks-white: #ffffff; + +$noclocks-gray: #f5f5f5; +$noclocks-gray-dark: #333333; +$noclocks-gray-light: #f9f9f9; + +$noclocks-blue: #007bff; +$noclocks-blue-dark: #0056b3; +$noclocks-blue-light: #cce5ff; + +$noclocks-green: #28a745; +$noclocks-green-dark: #218838; +$noclocks-green-light: #d4edda; + +$noclocks-red: #dc3545; +$noclocks-red-dark: #c82333; +$noclocks-red-light: #f8d7da; + +$noclocks-yellow: #ffc107; +$noclocks-yellow-dark: #e0a800; +$noclocks-yellow-light: #fff3cd; + +$noclocks-orange: #fd7e14; +$noclocks-orange-dark: #d66e00; +$noclocks-orange-light: #fff3cd; + +$noclocks-purple: #6f42c1; +$noclocks-purple-dark: #5a3a8a; +$noclocks-purple-light: #e2d5f5; + +$noclocks-cyan: #17a2b8; +$noclocks-cyan-dark: #117a8b; +$noclocks-cyan-light: #d1ecf1; + +$noclocks-pink: #e83e8c; +$noclocks-pink-dark: #bd185d; +$noclocks-pink-light: #f8d7da; + +$noclocks-teal: #20c997; +$noclocks-teal-dark: #198754; +$noclocks-teal-light: #d1ecf1; + +$noclocks-indigo: #6610f2; +$noclocks-indigo-dark: #4b0082; +$noclocks-indigo-light: #d6d3f1; + +$noclocks-lime: #a0d468; +$noclocks-lime-dark: #8cc152; +$noclocks-lime-light: #e6efc2; + +$noclocks-mint: #2ecc71; +$noclocks-mint-dark: #26a65b; +$noclocks-mint-light: #d9edc2; + +$noclocks-slate: #34495e; +$noclocks-slate-dark: #2c3e50; +$noclocks-slate-light: #ecf0f1; + +$noclocks-silver: #bdc3c7; +$noclocks-silver-dark: #90949c; +$noclocks-silver-light: #f5f7f9; + +$noclocks-gold: #f1c40f; +$noclocks-gold-dark: #d4ac0d; +$noclocks-gold-light: #fcf8e3; + +$noclocks-bronze: #d35400; +$noclocks-bronze-dark: #b03a00; +$noclocks-bronze-light: #f9e8d0; + +$noclocks-rose: #e74c3c; +$noclocks-rose-dark: #c0392b; +$noclocks-rose-light: #f5c6cb; + +$noclocks-crimson: #e74c3c; +$noclocks-crimson-dark: #c0392b; +$noclocks-crimson-light: #f5c6cb; + +$noclocks-maroon: #800000; +$noclocks-maroon-dark: #660000; +$noclocks-maroon-light: #f2f2f2; + +$noclocks-olive: #808000; +$noclocks-olive-dark: #666600; +$noclocks-olive-light: #f2f2f2; + +$noclocks-navy: #000080; +$noclocks-navy-dark: #000066; +$noclocks-navy-light: #f2f2f2; + +$noclocks-aqua: #00ffff; +$noclocks-aqua-dark: #00cccc; +$noclocks-aqua-light: #f2f2f2; + +$noclocks-salmon: #fa8072; +$noclocks-salmon-dark: #ff6347; +$noclocks-salmon-light: #f2f2f2; diff --git a/inst/assets/scss/_fonts.scss b/inst/assets/scss/_fonts.scss new file mode 100644 index 0000000..e69de29 diff --git a/inst/assets/scss/_sizes.scss b/inst/assets/scss/_sizes.scss new file mode 100644 index 0000000..e69de29 diff --git a/inst/assets/scss/custom.scss b/inst/assets/scss/custom.scss new file mode 100644 index 0000000..e69de29 diff --git a/inst/assets/scss/styles.scss b/inst/assets/scss/styles.scss new file mode 100644 index 0000000..e69de29 diff --git a/inst/config/.gitignore b/inst/config/.gitignore new file mode 100644 index 0000000..3427a25 --- /dev/null +++ b/inst/config/.gitignore @@ -0,0 +1,9 @@ +* +!.gitignore +!README.md +!auth0.yml +!config.template.yml +!credentials.template.json +!keeper.config.template.json +!oauth.template.json +!service-account.template.json diff --git a/inst/auth0.yml b/inst/config/auth0.yml similarity index 100% rename from inst/auth0.yml rename to inst/config/auth0.yml diff --git a/inst/pkgdown/_pkgdown.yml b/inst/pkgdown/_pkgdown.yml new file mode 100644 index 0000000..af672e8 --- /dev/null +++ b/inst/pkgdown/_pkgdown.yml @@ -0,0 +1,72 @@ +template: + bslib: + primary: "#222831" + base_font: {google: {family: "Inter", wght: [400, 600], ital: [0, 1]}} + code_font: {google: {family: "Fira Code", wght: [400, 600]}} + font_scale: 1.2 + fg: "#172431" + bg: "#fcfcfc" + border-radius: 0 + btn-border-radius: 3px + grid-gutter-width: 3rem + pkgdown-nav-height: 78px + +code: + width: 74 + +navbar: + type: dark + bg: none + +authors: + No Clocks: + href: https://noclocks.dev + html: No Clocks + RStudio: + href: https://www.rstudio.com + html: RStudio + Posit, PBC: + href: https://www.posit.co + html: >- + Posit + Posit Software, PBC: + href: https://www.posit.co + html: >- + Posit + + Alison Hill: + href: https://www.apreshill.com + Barret Schloerke: + href: http://schloerke.com + Carson Sievert: + href: https://cpsievert.me + Christophe Dervieux: + href: https://github.com/cderv + Davis Vaughan: + href: https://github.com/DavisVaughan + Gábor Csárdi: + href: https://github.com/gaborcsardi + Hadley Wickham: + href: https://hadley.nz + Hannah Frick: + href: https://frick.ws + Jennifer Bryan: + href: https://jennybryan.org + Jim Hester: + href: https://www.jimhester.com/ + Julia Silge: + href: https://juliasilge.com/ + Kirill Müller: + href: https://krlmlr.info + Lionel Henry: + href: https://github.com/lionel- + Max Kuhn: + href: https://github.com/topepo + Romain François: + href: https://github.com/romainfrancois + Thomas Lin Pedersen: + href: https://data-imaginist.com + Winston Chang: + href: https://github.com/wch + Yihui Xie: + href: https://yihui.org/ diff --git a/inst/pkgdown/assets/css/noclocks-pkgdown.css b/inst/pkgdown/assets/css/noclocks-pkgdown.css new file mode 100644 index 0000000..e69de29 diff --git a/inst/pkgdown/assets/css/noclocks-rmd.css b/inst/pkgdown/assets/css/noclocks-rmd.css new file mode 100644 index 0000000..e69de29 diff --git a/inst/pkgdown/templates/footer.html b/inst/pkgdown/templates/footer.html new file mode 100644 index 0000000..8983c5e --- /dev/null +++ b/inst/pkgdown/templates/footer.html @@ -0,0 +1,12 @@ + +