├── .dockerignore
├── vignettes
├── .gitignore
├── vscode_debug.Rmd
├── github_backend.Rmd
├── tidying_calendar.Rmd
└── streamer_info.Rmd
├── LICENSE
├── .env.example
├── renv
├── .gitignore
├── settings.dcf
└── activate.R
├── data
└── streamer_data.rda
├── inst
├── app
│ └── www
│ │ ├── favicon.ico
│ │ ├── timezone_detect.js
│ │ └── intro.md
└── golem-config.yml
├── R
├── _disable_autoload.R
├── utils-pipe.R
├── run_app.R
├── app_server.R
├── mod_feedback_fct_helpers.R
├── golem_utils_server.R
├── app_config.R
├── app_ui.R
├── mod_cal_entry.R
├── mod_feedback.R
├── golem_utils_ui.R
├── mod_cal_viewer_fct_helpers.R
└── mod_cal_viewer.R
├── prototyping
└── README.md
├── .gitignore
├── dev
├── app_manifest.txt
├── run_dev.R
├── python_code
│ └── hello.py
├── 03_deploy.R
├── hello.R
├── r_code
│ ├── hello.R
│ ├── wimpys-world-of-streamers-2021-06-08.ics
│ └── tmp.ics
├── 01_start.R
└── 02_dev.R
├── data-raw
├── yaml_files
│ ├── rctatman.yml
│ ├── favstats.yml
│ ├── kkent999.yml
│ ├── itsmetoeknee.yml
│ ├── wviechtb.yml
│ ├── kierisi.yml
│ ├── mcmullarkey.yml
│ ├── statsinthewild.yml
│ ├── tanho_.yml
│ ├── moriah_streamR.yml
│ ├── aklongmuir.yml
│ ├── lisadebruine.yml
│ ├── nickwan_datasci.yml
│ ├── rpodcast.yml
│ ├── UCeiiqmVK07qhY-wvg3IZiZQ.yml
│ ├── waylonwalker.yml
│ ├── thecedarprince.yml
│ ├── theeatgamelove.yml
│ └── aftonsteps.yml
└── streamer_data.R
├── .Rbuildignore
├── .Renviron.example
├── .lintr
├── .devcontainer
├── rstudio_docker
│ ├── .env
│ ├── rstudio.env
│ └── Dockerfile
├── launch.json
├── .env
├── library-scripts
│ ├── r-packages-setup.sh
│ ├── .Rprofile-vscode
│ └── common-debian.sh
├── keybindings.json
├── docker-compose.yml
├── rstudio_config_dir
│ └── rstudio-prefs.json
├── devcontainer.json
└── Dockerfile
├── app.R
├── shinycal.Rproj
├── man
├── import_cal.Rd
├── pipe.Rd
└── run_app.Rd
├── NAMESPACE
├── LICENSE.md
├── DESCRIPTION
├── README.md
├── .github
└── CONTRIBUTING.md
├── README.Rmd
├── NEWS.md
├── testapp
└── app.R
├── .Rprofile
├── CODE_OF_CONDUCT.md
└── renv.lock
/.dockerignore:
--------------------------------------------------------------------------------
1 | .git/
2 |
--------------------------------------------------------------------------------
/vignettes/.gitignore:
--------------------------------------------------------------------------------
1 | *.html
2 | *.R
3 | .Renviron
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | YEAR: 2021
2 | COPYRIGHT HOLDER: Eric Nantz
3 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | NEXTCLOUD_USER="username"
2 | NEXTCLOUD_PASSWORD="mypassword"
--------------------------------------------------------------------------------
/renv/.gitignore:
--------------------------------------------------------------------------------
1 | library/
2 | local/
3 | lock/
4 | python/
5 | staging/
6 |
--------------------------------------------------------------------------------
/data/streamer_data.rda:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rpodcast/shinycal/HEAD/data/streamer_data.rda
--------------------------------------------------------------------------------
/inst/app/www/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rpodcast/shinycal/HEAD/inst/app/www/favicon.ico
--------------------------------------------------------------------------------
/R/_disable_autoload.R:
--------------------------------------------------------------------------------
1 | # Disabling shiny autoload
2 |
3 | # See ?shiny::loadSupport for more information
4 |
--------------------------------------------------------------------------------
/prototyping/README.md:
--------------------------------------------------------------------------------
1 | Use this directory for exploring quick things. By default the contents of this directory will not be version controlled!
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .Renviron
2 | .Rhistory
3 | .Rproj.user
4 | .env
5 | inst/doc
6 | prototyping/*
7 | !prototyping/README.md
8 | README.html
9 | node_modules/*
--------------------------------------------------------------------------------
/dev/app_manifest.txt:
--------------------------------------------------------------------------------
1 | data
2 | data-raw
3 | inst
4 | man
5 | R
6 | app.R
7 | DESCRIPTION
8 | NAMESPACE
9 | NEWS.md
10 | README.md
11 | .Renviron
--------------------------------------------------------------------------------
/data-raw/yaml_files/rctatman.yml:
--------------------------------------------------------------------------------
1 | user_id: rctatman
2 | name: Rachael Tatman
3 | twitter_id: rctatman
4 | platform: twitch
5 | categories:
6 | - datascience
--------------------------------------------------------------------------------
/data-raw/yaml_files/favstats.yml:
--------------------------------------------------------------------------------
1 | user_id: favstats
2 | name: Fabio Votta
3 | twitter_id: favstats
4 | platform: twitch
5 | categories:
6 | - datascience
7 | - r
--------------------------------------------------------------------------------
/data-raw/yaml_files/kkent999.yml:
--------------------------------------------------------------------------------
1 | user_id: kkent999
2 | name: Kevin Kent
3 | twitter_id: kevin_m_kent
4 | platform: twitch
5 | categories:
6 | - datascience
7 | - r
--------------------------------------------------------------------------------
/data-raw/yaml_files/itsmetoeknee.yml:
--------------------------------------------------------------------------------
1 | user_id: itsmetoeknee
2 | name: Tony Elhabr
3 | twitter_id: TonyElHabr
4 | platform: twitch
5 | categories:
6 | - datascience
7 | - r
--------------------------------------------------------------------------------
/data-raw/yaml_files/wviechtb.yml:
--------------------------------------------------------------------------------
1 | user_id: wviechtb
2 | name: Wolfgang Viechtbauer
3 | twitter_id: wviechtb
4 | platform: twitch
5 | categories:
6 | - datascience
7 | - r
--------------------------------------------------------------------------------
/data-raw/yaml_files/kierisi.yml:
--------------------------------------------------------------------------------
1 | user_id: kierisi
2 | name: Jesse Mostipak
3 | twitter_id: kierisi
4 | platform: twitch
5 | categories:
6 | - r
7 | - datascience
8 | - gaming
--------------------------------------------------------------------------------
/data-raw/yaml_files/mcmullarkey.yml:
--------------------------------------------------------------------------------
1 | user_id: mcmullarkey
2 | name: Michael Mullarkey
3 | twitter_id: mcmullarkey
4 | platform: twitch
5 | categories:
6 | - datascience
7 | - r
--------------------------------------------------------------------------------
/data-raw/yaml_files/statsinthewild.yml:
--------------------------------------------------------------------------------
1 | user_id: statsinthewild
2 | name: Greg Matthews
3 | twitter_id: statsinthewild
4 | platform: twitch
5 | categories:
6 | - datascience
7 | - r
--------------------------------------------------------------------------------
/data-raw/yaml_files/tanho_.yml:
--------------------------------------------------------------------------------
1 | user_id: tanho_
2 | name: Tan Ho
3 | twitter_id: _TanHo
4 | platform: twitch
5 | categories:
6 | - r
7 | - datascience
8 | - shiny
9 | - sports
--------------------------------------------------------------------------------
/data-raw/yaml_files/moriah_streamR.yml:
--------------------------------------------------------------------------------
1 | user_id: moriah_streamR
2 | name: Moriah Taylor
3 | twitter_id: moriah_taylor58
4 | platform: twitch
5 | categories:
6 | - r
7 | - datascience
--------------------------------------------------------------------------------
/data-raw/yaml_files/aklongmuir.yml:
--------------------------------------------------------------------------------
1 | user_id: aklongmuir
2 | name: Alyssa Longmuir
3 | twitter_id: alyssastweeting
4 | platform: twitch
5 | categories:
6 | - r
7 | - datascience
8 | - sports
--------------------------------------------------------------------------------
/data-raw/yaml_files/lisadebruine.yml:
--------------------------------------------------------------------------------
1 | user_id: lisadebruine
2 | name: Lisa DeBruine
3 | twitter_id: lisadebruine
4 | platform: twitch
5 | categories:
6 | - r
7 | - datascience
8 | - shiny
--------------------------------------------------------------------------------
/data-raw/yaml_files/nickwan_datasci.yml:
--------------------------------------------------------------------------------
1 | user_id: nickwan_datasci
2 | name: Nick Wan
3 | twitter_id: nickwan
4 | platform: twitch
5 | categories:
6 | - python
7 | - datascience
8 | - gaming
--------------------------------------------------------------------------------
/data-raw/yaml_files/rpodcast.yml:
--------------------------------------------------------------------------------
1 | user_id: rpodcast
2 | name: Eric Nantz
3 | twitter_id: thercast
4 | platform: twitch
5 | categories:
6 | - r
7 | - datascience
8 | - shiny
9 | - linux
--------------------------------------------------------------------------------
/.Rbuildignore:
--------------------------------------------------------------------------------
1 | ^renv$
2 | ^renv\.lock$
3 | ^LICENSE\.md$
4 | ^.*\.Rproj$
5 | ^\.Rproj\.user$
6 | ^README\.Rmd$
7 | ^CODE_OF_CONDUCT\.md$
8 | ^app\.R$
9 | ^rsconnect$
10 | ^data-raw$
11 |
--------------------------------------------------------------------------------
/.Renviron.example:
--------------------------------------------------------------------------------
1 | NEXTCLOUD_USER="username"
2 | NEXTCLOUD_PASSWORD="mypassword"
3 | TWITCH_CLIENT_ID=aaaaaaaaaaa
4 | TWITCH_SECRET=bbbbbbbbbbbbbbbbbbb
5 | YOUTUBE_API_KEY=hhhhhhhhhhhhhhhhhhhhhhhhh
--------------------------------------------------------------------------------
/data-raw/yaml_files/UCeiiqmVK07qhY-wvg3IZiZQ.yml:
--------------------------------------------------------------------------------
1 | user_id: UCeiiqmVK07qhY-wvg3IZiZQ
2 | name: David Robinson
3 | twitter_id: drob
4 | platform: youtube
5 | categories:
6 | - r
7 | - datascience
--------------------------------------------------------------------------------
/data-raw/yaml_files/waylonwalker.yml:
--------------------------------------------------------------------------------
1 | user_id: waylonwalker
2 | name: Waylon Walker
3 | twitter_id: waylonwalker
4 | platform: twitch
5 | categories:
6 | - datascience
7 | - python
8 | - linux
--------------------------------------------------------------------------------
/data-raw/yaml_files/thecedarprince.yml:
--------------------------------------------------------------------------------
1 | user_id: thecedarprince
2 | name: Jacob Zelko
3 | twitter_id: jacob_zelko
4 | platform: twitch
5 | categories:
6 | - datascience
7 | - r
8 | - python
9 | - julia
--------------------------------------------------------------------------------
/data-raw/yaml_files/theeatgamelove.yml:
--------------------------------------------------------------------------------
1 | user_id: theeatgamelove
2 | name: Kyle Harris and Alexis Meskowski
3 | twitter_id: theeatgamelove
4 | platform: twitch
5 | categories:
6 | - r
7 | - datascience
8 | - gaming
--------------------------------------------------------------------------------
/.lintr:
--------------------------------------------------------------------------------
1 | linters: with_defaults(
2 | line_length_linter(120),
3 | commented_code_linter = NULL,
4 | object_usage_linter = NULL,
5 | trailing_blank_lines_linter = NULL,
6 | trailing_whitespace_linter = NULL
7 | )
8 |
--------------------------------------------------------------------------------
/.devcontainer/rstudio_docker/.env:
--------------------------------------------------------------------------------
1 | RENV_PATHS_CACHE_HOST="/opt/local/renv/cache_rstudio"
2 | RENV_PATHS_CACHE_CONTAINER="/renv/cache"
3 | RENV_PATHS_CACHE="/renv/cache"
4 | USER="eric"
5 | USERID=1000
6 | GROUPID=1000
7 | PASSWORD=1rstudio
--------------------------------------------------------------------------------
/.devcontainer/rstudio_docker/rstudio.env:
--------------------------------------------------------------------------------
1 | RENV_PATHS_CACHE_HOST=/opt/local/renv/cache_rstudio
2 | RENV_PATHS_CACHE_CONTAINER=/renv/cache
3 | RENV_PATHS_CACHE=/renv/cache
4 | USER=eric
5 | USERID=1000
6 | GROUPID=1000
7 | PASSWORD=1rstudio
--------------------------------------------------------------------------------
/data-raw/yaml_files/aftonsteps.yml:
--------------------------------------------------------------------------------
1 | user_id: aftonsteps
2 | name: Afton Coombs
3 | twitter_id: aftonsteps
4 | platform: twitch
5 | bgColor: "#008080"
6 | color: "white"
7 | categories:
8 | - r
9 | - datascience
10 | - shiny
--------------------------------------------------------------------------------
/renv/settings.dcf:
--------------------------------------------------------------------------------
1 | external.libraries:
2 | ignored.packages:
3 | package.dependency.fields: Imports, Depends, LinkingTo
4 | r.version:
5 | snapshot.type: implicit
6 | use.cache: TRUE
7 | vcs.ignore.library: TRUE
8 | vcs.ignore.local: TRUE
9 |
--------------------------------------------------------------------------------
/.devcontainer/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "R-Debugger",
3 | "request": "launch",
4 | "name": "Debug Shiny App",
5 | "debugMode": "file",
6 | "workingDirectory": "${workspaceFolder}",
7 | "file": "${workspaceFolder}/dev/run_dev.R",
8 | "allowGlobalDebugging": false
9 | }
10 |
--------------------------------------------------------------------------------
/.devcontainer/.env:
--------------------------------------------------------------------------------
1 |
2 | RENV_PATHS_CACHE_HOST=/opt/local/renv/cache
3 | RENV_PATHS_CACHE_CONTAINER=/renv/cache
4 | RENV_PATHS_CACHE=/renv/cache
5 | USER=eric
6 | USERID=1000
7 | GROUPID=1000
8 | PASSWORD=1rstudio
9 | VOLUME_PATH=/home/eric/shinydevseries_projects
10 | LOCAL_PORT=7111
11 | CONTAINER_NAME=shinycal
12 |
--------------------------------------------------------------------------------
/app.R:
--------------------------------------------------------------------------------
1 | # Launch the ShinyApp (Do not remove this comment)
2 | # To deploy, run: rsconnect::deployApp()
3 | # Or use the blue button on top of this file
4 |
5 | pkgload::load_all(export_all = FALSE,helpers = FALSE,attach_testthat = FALSE)
6 | options( "golem.app.prod" = TRUE)
7 | shinycal::run_app() # add parameters here (if any)
8 |
--------------------------------------------------------------------------------
/dev/run_dev.R:
--------------------------------------------------------------------------------
1 | # Set options here
2 | options(golem.app.prod = FALSE) # TRUE = production mode, FALSE = development mode
3 |
4 | # Detach all loaded packages and clean your environment
5 | golem::detach_all_attached()
6 | # rm(list=ls(all.names = TRUE))
7 |
8 | # Document and reload your package
9 | golem::document_and_reload()
10 |
11 | # Run the application
12 | run_app()
13 |
--------------------------------------------------------------------------------
/shinycal.Rproj:
--------------------------------------------------------------------------------
1 | Version: 1.0
2 |
3 | RestoreWorkspace: Default
4 | SaveWorkspace: Default
5 | AlwaysSaveHistory: Default
6 |
7 | EnableCodeIndexing: Yes
8 | UseSpacesForTab: Yes
9 | NumSpacesForTab: 2
10 | Encoding: UTF-8
11 |
12 | RnwWeave: Sweave
13 | LaTeX: pdfLaTeX
14 |
15 | BuildType: Package
16 | PackageUseDevtools: Yes
17 | PackageInstallArgs: --no-multiarch --with-keep.source
18 |
--------------------------------------------------------------------------------
/R/utils-pipe.R:
--------------------------------------------------------------------------------
1 | #' Pipe operator
2 | #'
3 | #' See \code{magrittr::\link[magrittr:pipe]{\%>\%}} for details.
4 | #'
5 | #' @name %>%
6 | #' @rdname pipe
7 | #' @keywords internal
8 | #' @export
9 | #' @importFrom magrittr %>%
10 | #' @usage lhs \%>\% rhs
11 | #' @param lhs A value or the magrittr placeholder.
12 | #' @param rhs A function call using the magrittr semantics.
13 | #' @return The result of calling `rhs(lhs)`.
14 | NULL
15 |
--------------------------------------------------------------------------------
/inst/golem-config.yml:
--------------------------------------------------------------------------------
1 | default:
2 | golem_name: shinycal
3 | golem_version: 0.8
4 | app_prod: no
5 | cal_host: "nextcloud.r-podcastdev.link"
6 | cal_base_url: "https://nextcloud.r-podcastdev.link/remote.php/dav/calendars/shinycal"
7 | streamer_data_repo: "git@github.com:rpodcast/streamer_data_dev.git"
8 | production:
9 | app_prod: yes
10 | streamer_data_repo: "git@github.com:rpodcast/streamer_data.git"
11 | dev:
12 | golem_wd: !expr here::here()
13 |
--------------------------------------------------------------------------------
/.devcontainer/library-scripts/r-packages-setup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # install depedencies for httpgd
4 | apt-get -y install --no-install-recommends libfreetype6-dev libfontconfig1-dev
5 |
6 | # Use littler to install packages
7 | install2.r languageserver renv remotes
8 | installGithub.r nx10/httpgd
9 |
10 | # establish renv environment variables in users .Renviron file
11 | mkdir -p /renv/cache
12 | echo "RENV_PATHS_CACHE=/renv/cache" >> /usr/local/lib/R/etc/Renviron
13 |
--------------------------------------------------------------------------------
/inst/app/www/timezone_detect.js:
--------------------------------------------------------------------------------
1 | $( document ).ready(function() {
2 | var d = new Date();
3 | var target = $('#clientTime');
4 | var offset = $('#clientTimeOffset');
5 | var timezone = $('#clientZone')
6 |
7 | offset.val(d.getTimezoneOffset());
8 | offset.trigger("change");
9 | target.val(d.toLocaleString());
10 | target.trigger("change");
11 |
12 | timezone.val(Intl.DateTimeFormat().resolvedOptions().timeZone);
13 | timezone.trigger("change")
14 | });
15 |
--------------------------------------------------------------------------------
/.devcontainer/keybindings.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "key": "ctrl+v",
4 | "command": "pasteAndIndent.action",
5 | "when": "editorTextFocus && !editorReadonly"
6 | },
7 | {
8 | "key": "ctrl+v",
9 | "command": "editor.action.clipboardPasteAction",
10 | "when": "!editorTextFocus"
11 | },
12 | {
13 | "key": "ctrl+shift+v",
14 | "command": "editor.action.clipboardPasteAction",
15 | "when": "editorTextFocus && !editorReadonly"
16 | }
17 | ]
--------------------------------------------------------------------------------
/man/import_cal.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/mod_cal_viewer_fct_helpers.R
3 | \name{import_cal}
4 | \alias{import_cal}
5 | \title{Import calendar directly from server}
6 | \usage{
7 | import_cal(cal_slug = "wimpys-world-of-streamers", cal_base_url = NULL)
8 | }
9 | \arguments{
10 | \item{cal_slug}{string for URL slug of calendar.}
11 | }
12 | \value{
13 | data frame with calendar event contents
14 | }
15 | \description{
16 | Import calendar directly from server
17 | }
18 |
--------------------------------------------------------------------------------
/man/pipe.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/utils-pipe.R
3 | \name{\%>\%}
4 | \alias{\%>\%}
5 | \title{Pipe operator}
6 | \usage{
7 | lhs \%>\% rhs
8 | }
9 | \arguments{
10 | \item{lhs}{A value or the magrittr placeholder.}
11 |
12 | \item{rhs}{A function call using the magrittr semantics.}
13 | }
14 | \value{
15 | The result of calling `rhs(lhs)`.
16 | }
17 | \description{
18 | See \code{magrittr::\link[magrittr:pipe]{\%>\%}} for details.
19 | }
20 | \keyword{internal}
21 |
--------------------------------------------------------------------------------
/inst/app/www/intro.md:
--------------------------------------------------------------------------------
1 | In the last few years, we have seen an huge uptick in the number of data science professionals and hobbyists who like to stream their projects and share their knowledge with the community! This calendar is my small attempt to contribute back to this awesome community so you can see what great streams are happening!
2 |
3 | Check out Jesse Mostipak's [Data Science Streamers](https://www.jessemaegan.com/blog/2021-05-28-data-science-twitch-streamers-round-up/) blog post for a great roundup of the content creators seen on this calendar!
--------------------------------------------------------------------------------
/.devcontainer/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.4'
2 |
3 | services:
4 | shinycal:
5 | build:
6 | context: ./rstudio_docker
7 | container_name: ${CONTAINER_NAME}
8 | volumes:
9 | - ${VOLUME_PATH}:${VOLUME_PATH}
10 | - /home/${USER}/.ssh:/home/${USER}/.ssh
11 | - ${RENV_PATHS_CACHE_HOST}:${RENV_PATHS_CACHE_CONTAINER}
12 | - ${VOLUME_PATH}/.devcontainer/rstudio_config_dir:/home/${USER}/.config/rstudio
13 | restart:
14 | unless-stopped
15 | ports:
16 | - ${LOCAL_PORT}:8787
17 | env_file: .env
18 |
19 |
--------------------------------------------------------------------------------
/R/run_app.R:
--------------------------------------------------------------------------------
1 | #' Run the Shiny Application
2 | #'
3 | #' @param ... arguments to pass to golem_opts.
4 | #' See `?golem::get_golem_options` for more details.
5 | #' @inheritParams shiny::shinyApp
6 | #'
7 | #' @export
8 | #' @importFrom shiny shinyApp
9 | #' @importFrom golem with_golem_options
10 | run_app <- function(
11 | onStart = NULL,
12 | options = list(),
13 | enableBookmarking = NULL,
14 | uiPattern = "/",
15 | ...
16 | ) {
17 | with_golem_options(
18 | app = shinyApp(
19 | ui = app_ui,
20 | server = app_server,
21 | onStart = onStart,
22 | options = options,
23 | enableBookmarking = enableBookmarking,
24 | uiPattern = uiPattern
25 | ),
26 | golem_opts = list(...)
27 | )
28 | }
29 |
--------------------------------------------------------------------------------
/vignettes/vscode_debug.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "vscode debug"
3 | output: html_document
4 | ---
5 |
6 | ## Obtaining metadata associated with streamers
7 |
8 | The goal of this vignette is to illustrate how we can get interesting metadata associated with each streamer via the Twitch and YouTube APIs.
9 |
10 | ## Setup
11 |
12 | ```{r setup, include = FALSE}
13 | library(twitchr)
14 | library(httr)
15 | library(calendar)
16 | library(caldav)
17 | library(dplyr)
18 | library(yaml)
19 |
20 | print(getwd())
21 | print(here::here())
22 | readRenviron(".Renviron")
23 | ```
24 |
25 | ## Twitch streamer metadata
26 |
27 | Hello during my stream
28 |
29 | ```{r twitchauth, eval=TRUE}
30 | # set up authentication via environment variables
31 | twitch_auth()
32 | ```
33 |
--------------------------------------------------------------------------------
/R/app_server.R:
--------------------------------------------------------------------------------
1 | #' The application server-side
2 | #'
3 | #' @param input,output,session Internal parameters for {shiny}.
4 | #' DO NOT REMOVE.
5 | #' @import shiny
6 | #' @noRd
7 | app_server <- function( input, output, session ) {
8 |
9 | # obtain custom inputs derived from javascript time change function
10 | output$server <- renderText({ c("Server time:", as.character(Sys.time()), as.character(Sys.timezone())) })
11 |
12 | session$userData$time <- reactive({
13 | format(lubridate::mdy_hms(as.character(input$clientTime)), "%d/%m/%Y; %H:%M:%S")
14 | })
15 |
16 | session$userData$offset <- reactive({
17 | input$clientTimeOffset
18 | })
19 |
20 | session$userData$zone <- reactive({
21 | input$clientZone
22 | })
23 |
24 | # specific to shinylogs
25 | #shinylogs::track_usage(storage_mode = store_null())
26 |
27 | # Your application server logic
28 | mod_cal_viewer_server("cal_viewer_ui_1")
29 | #mod_cal_entry_server("cal_entry_ui_1")
30 | mod_feedback_server("feedback_ui_1")
31 | }
32 |
--------------------------------------------------------------------------------
/.devcontainer/rstudio_config_dir/rstudio-prefs.json:
--------------------------------------------------------------------------------
1 | {
2 | "panes": {
3 | "quadrants": [
4 | "Source",
5 | "TabSet1",
6 | "Console",
7 | "TabSet2"
8 | ],
9 | "tabSet1": [
10 | "History",
11 | "Presentation"
12 | ],
13 | "tabSet2": [
14 | "Environment",
15 | "Files",
16 | "Plots",
17 | "Connections",
18 | "Packages",
19 | "Help",
20 | "Build",
21 | "VCS",
22 | "Tutorial",
23 | "Viewer"
24 | ],
25 | "console_left_on_top": false,
26 | "console_right_on_top": true
27 | },
28 | "save_workspace": "never",
29 | "load_workspace": false,
30 | "auto_append_newline": true,
31 | "font_size_points": 14,
32 | "editor_theme": "Tomorrow Night 80s",
33 | "posix_terminal_shell": "bash",
34 | "default_project_location": "~/r_dev_projects",
35 | "initial_working_directory": "~"
36 | }
--------------------------------------------------------------------------------
/NAMESPACE:
--------------------------------------------------------------------------------
1 | # Generated by roxygen2: do not edit by hand
2 |
3 | export("%>%")
4 | export(import_cal)
5 | export(run_app)
6 | import(caldav)
7 | import(shiny)
8 | import(shinylogs)
9 | import(shinyvalidate)
10 | import(toastui)
11 | importFrom(calendar,ic_read)
12 | importFrom(calendar,ical)
13 | importFrom(clock,as_naive_time)
14 | importFrom(clock,as_zoned_time)
15 | importFrom(clock,date_time_parse)
16 | importFrom(clock,zoned_time_set_zone)
17 | importFrom(dplyr,case_when)
18 | importFrom(dplyr,filter)
19 | importFrom(dplyr,left_join)
20 | importFrom(dplyr,mutate)
21 | importFrom(dplyr,select)
22 | importFrom(golem,activate_js)
23 | importFrom(golem,add_resource_path)
24 | importFrom(golem,bundle_resources)
25 | importFrom(golem,favicon)
26 | importFrom(golem,with_golem_options)
27 | importFrom(magrittr,"%>%")
28 | importFrom(purrr,map)
29 | importFrom(purrr,map2)
30 | importFrom(shiny,HTML)
31 | importFrom(shiny,NS)
32 | importFrom(shiny,column)
33 | importFrom(shiny,shinyApp)
34 | importFrom(shiny,tagAppendAttributes)
35 | importFrom(shiny,tagList)
36 | importFrom(shiny,tags)
37 | importFrom(twitchr,get_users)
38 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | # MIT License
2 |
3 | Copyright (c) 2021 Eric Nantz
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/DESCRIPTION:
--------------------------------------------------------------------------------
1 | Package: shinycal
2 | Title: The data science streamRs calendar
3 | Version: 0.8
4 | Authors@R: person('Eric', 'Nantz', email = 'thercast@gmail.com', role = c('cre', 'aut'))
5 | Description: A shiny application serving an interactive calendar for
6 | data science streams on various platforms such as Twitch.
7 | License: MIT + file LICENSE
8 | Imports:
9 | calendar,
10 | config (>= 0.3.1),
11 | dplyr,
12 | golem (>= 0.3.1),
13 | magrittr,
14 | pkgload,
15 | purrr,
16 | shiny (>= 1.6.0),
17 | clock,
18 | shinylogs,
19 | caldav (>= 0.1.0),
20 | toastui (>= 0.1.2.9300),
21 | tidyr,
22 | fs,
23 | stringr,
24 | readr,
25 | httr,
26 | tibble,
27 | twitchr (>= 0.1.0),
28 | colourpicker,
29 | shinyWidgets,
30 | rvest,
31 | shinyvalidate,
32 | gh
33 | Encoding: UTF-8
34 | LazyData: true
35 | RoxygenNote: 7.1.1
36 | URL: https://github.com/rpodcast/shinycal
37 | BugReports: https://github.com/rpodcast/shinycal/issues
38 | Suggests:
39 | rmarkdown,
40 | knitr
41 | VignetteBuilder: knitr
42 | Depends:
43 | R (>= 2.10)
44 | Remotes:
45 | petermeissner/caldav,
46 | dreamRs/toastui,
47 | KoderKow/twitchr
48 |
--------------------------------------------------------------------------------
/R/mod_feedback_fct_helpers.R:
--------------------------------------------------------------------------------
1 | get_issue_labels <- function(owner = "rpodcast", repo = "shinycal") {
2 | issues_raw <- gh::gh("GET /repos/{owner}/{repo}/labels", owner = "rpodcast", repo = "shinycal")
3 |
4 | labels_df <- tibble::tibble(x = issues_raw) %>%
5 | tidyr::unnest_wider(1)
6 |
7 | labels <- dplyr::pull(labels_df, name)
8 |
9 | return(labels)
10 | }
11 |
12 | submit_issue <- function(
13 | issue_title,
14 | issue_labels,
15 | issue_description,
16 | repo = "shinycal",
17 | owner = "rpodcast",
18 | add_issues = "user feedback"
19 | ) {
20 | # append name and date to end of issue description
21 | version <- golem::get_golem_version()
22 | issue_date <- Sys.Date()
23 |
24 | end_text <- glue::glue("\n\n\n\nFile from shinycal version {version} on {issue_date}")
25 |
26 | issue_description <- paste0(issue_description, end_text)
27 |
28 | # add additional labels
29 | issue_labels <- c(issue_labels, add_issues)
30 |
31 | # submit
32 | issue_post <- gh::gh(
33 | glue::glue("POST /repos/{owner}/{repo}/issues"),
34 | title = issue_title,
35 | body = issue_description,
36 | assignee = "rpodcast",
37 | labels = issue_labels
38 | )
39 |
40 | return(issue_post)
41 | }
42 |
43 |
--------------------------------------------------------------------------------
/R/golem_utils_server.R:
--------------------------------------------------------------------------------
1 | #' Inverted versions of in, is.null and is.na
2 | #'
3 | #' @noRd
4 | #'
5 | #' @examples
6 | #' 1 %not_in% 1:10
7 | #' not_null(NULL)
8 | `%not_in%` <- Negate(`%in%`)
9 |
10 | not_null <- Negate(is.null)
11 |
12 | not_na <- Negate(is.na)
13 |
14 | #' Removes the null from a vector
15 | #'
16 | #' @noRd
17 | #'
18 | #' @example
19 | #' drop_nulls(list(1, NULL, 2))
20 | drop_nulls <- function(x){
21 | x[!sapply(x, is.null)]
22 | }
23 |
24 | #' If x is `NULL`, return y, otherwise return x
25 | #'
26 | #' @param x,y Two elements to test, one potentially `NULL`
27 | #'
28 | #' @noRd
29 | #'
30 | #' @examples
31 | #' NULL %||% 1
32 | "%||%" <- function(x, y){
33 | if (is.null(x)) {
34 | y
35 | } else {
36 | x
37 | }
38 | }
39 |
40 | #' If x is `NA`, return y, otherwise return x
41 | #'
42 | #' @param x,y Two elements to test, one potentially `NA`
43 | #'
44 | #' @noRd
45 | #'
46 | #' @examples
47 | #' NA %||% 1
48 | "%|NA|%" <- function(x, y){
49 | if (is.na(x)) {
50 | y
51 | } else {
52 | x
53 | }
54 | }
55 |
56 | #' Typing reactiveValues is too long
57 | #'
58 | #' @inheritParams reactiveValues
59 | #' @inheritParams reactiveValuesToList
60 | #'
61 | #' @noRd
62 | rv <- shiny::reactiveValues
63 | rvtl <- shiny::reactiveValuesToList
64 |
65 |
--------------------------------------------------------------------------------
/R/app_config.R:
--------------------------------------------------------------------------------
1 | #' Access files in the current app
2 | #'
3 | #' NOTE: If you manually change your package name in the DESCRIPTION,
4 | #' don't forget to change it here too, and in the config file.
5 | #' For a safer name change mechanism, use the `golem::set_golem_name()` function.
6 | #'
7 | #' @param ... character vectors, specifying subdirectory and file(s)
8 | #' within your package. The default, none, returns the root of the app.
9 | #'
10 | #' @noRd
11 | app_sys <- function(...){
12 | system.file(..., package = "shinycal")
13 | }
14 |
15 |
16 | #' Read App Config
17 | #'
18 | #' @param value Value to retrieve from the config file.
19 | #' @param config GOLEM_CONFIG_ACTIVE value. If unset, R_CONFIG_ACTIVE.
20 | #' If unset, "default".
21 | #' @param use_parent Logical, scan the parent directory for config file.
22 | #'
23 | #' @noRd
24 | get_golem_config <- function(
25 | value,
26 | config = Sys.getenv(
27 | "GOLEM_CONFIG_ACTIVE",
28 | Sys.getenv(
29 | "R_CONFIG_ACTIVE",
30 | "default"
31 | )
32 | ),
33 | use_parent = TRUE
34 | ){
35 | config::get(
36 | value = value,
37 | config = config,
38 | # Modify this if your config file is somewhere else:
39 | file = app_sys("golem-config.yml"),
40 | use_parent = use_parent
41 | )
42 | }
43 |
44 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | # The Data Science StreamRs Calendar\!
5 |
6 |
7 |
8 |
9 |
10 | In the last few years, we have seen an huge uptick in the number of data
11 | science professionals and hobbyists who like to stream their projects
12 | and share their knowledge with the community\! This calendar is my small
13 | attempt to contribute back to this awesome community so you can see what
14 | great streams are happening\!
15 |
16 | This repository contains the Shiny app providing the streamers calendar,
17 | created with the [`{golem}`](https://golemverse.org/) package. In
18 | addition, this app would not be possible without the
19 | [`{toastui}`](https://github.com/dreamRs/toastui) package by the fine
20 | friends at DreamRs.
21 |
22 | ## Installation
23 |
24 | You can install the development version from
25 | [GitHub](https://github.com/) with:
26 |
27 | ``` r
28 | # install.packages("remotes")
29 | remotes::install_github("rpodcast/shinycal")
30 | ```
31 |
32 | ## Code of Conduct
33 |
34 | Please note that the shinycal project is released with a [Contributor
35 | Code of
36 | Conduct](https://contributor-covenant.org/version/2/0/CODE_OF_CONDUCT.html).
37 | By contributing to this project, you agree to abide by its terms.
38 |
--------------------------------------------------------------------------------
/data-raw/streamer_data.R:
--------------------------------------------------------------------------------
1 | ## code to prepare `streamer_data` dataset goes here
2 | devtools::load_all()
3 | # yml_file <- "data-raw/streamers.yml"
4 | # yml_data <- yaml::read_yaml(file = yml_file)
5 |
6 | yml_files <- fs::dir_ls("data-raw/yaml_files")
7 |
8 | yml_data <- purrr::map(yml_files, ~yaml::read_yaml(file = .x))
9 |
10 | #tibble::tibble(yml_data2) %>% tidyr::unnest_wider(1)
11 |
12 | # set up authentication via environment variables
13 | twitchr::twitch_auth()
14 |
15 | streamer_data <- tibble::tibble(yml_data) %>%
16 | tidyr::unnest_wider(1) %>%
17 | dplyr::filter(platform == "twitch") %>%
18 | dplyr::mutate(user_data = purrr::map(user_id, ~get_twitch_id(user_name = .x))) %>%
19 | tidyr::unnest(cols = user_data) %>%
20 | #dplyr::mutate(id = purrr::map_chr(user_id, ~get_twitch_id(user_name = .x))) %>%
21 | dplyr::mutate(schedule_data = purrr::map(id, ~get_twitch_schedule(.x)),
22 | videos_data = purrr::map(id, ~get_twitch_videos(.x)))
23 |
24 | usethis::use_data(streamer_data, overwrite = TRUE)
25 |
26 |
27 | # streamer_data2 <- streamer_data %>%
28 | # dplyr::mutate(schedule_data = purrr::map(id, ~get_twitch_schedule(.x)))
29 |
30 | # streamer_data2 %>%
31 | # tidyr::unnest(schedule_data) %>%
32 | # mutate(calendarId = 1) %>%
33 | # mutate(id = seq_len(dplyr::n()))
--------------------------------------------------------------------------------
/.devcontainer/library-scripts/.Rprofile-vscode:
--------------------------------------------------------------------------------
1 | # setup if using with vscode and R plugin
2 | if (Sys.getenv("TERM_PROGRAM") == "vscode") {
3 | source(file.path(Sys.getenv(if (.Platform$OS.type == "windows") "USERPROFILE" else "HOME"), ".vscode-R", "init.R"))
4 | }
5 | source("renv/activate.R")
6 |
7 | if (Sys.getenv("TERM_PROGRAM") == "vscode") {
8 | # obtain list of packages in renv library currently
9 | project <- renv:::renv_project_resolve(NULL)
10 | lib_packages <- names(unclass(renv:::renv_diagnostics_packages_library(project))$Packages)
11 |
12 | # detect whether key packages are already installed
13 | # was: !require("languageserver")
14 | if (!"languageserver" %in% lib_packages) {
15 | message("installing languageserver package")
16 | renv::install("languageserver")
17 | }
18 |
19 | if (!"httpgd" %in% lib_packages) {
20 | message("installing httpgd package")
21 | renv::install("nx10/httpgd")
22 | }
23 |
24 | if (!"vscDebugger" %in% lib_packages) {
25 | message("installation vscDebugger package")
26 | renv::install("ManuelHentschel/vscDebugger@v0.4.5")
27 | }
28 |
29 | # use the new httpgd plotting device
30 | options(vsc.plot = FALSE)
31 | options(device = function(...) {
32 | httpgd:::hgd()
33 | .vsc.browser(httpgd::hgd_url(), viewer = "Beside")
34 | })
35 | }
36 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to **shinycal**
2 |
3 | There are many ways to contribute to the ongoing development of the **shinycal** package! Some contributions can be rather easy to do (e.g., fixing typos, improving documentation, filing issues for feature requests or problems, etc.) whereas other contributions can require more time and patience (like answering questions and submitting pull requests with code changes). Just know that that help provided in any capacity is very much appreciated. :)
4 |
5 | ## Filing Issues
6 |
7 | If you believe you found a bug with the application, please include details on how you arrived at the bug (i.e. did you click on a certain input, choose a specific value, etc). If you found issues in the code itself, please include specific details on the location of the bug in the code base, and if possible create a reprex that demonstrates the issue. Details on creating a reprex in a Shiny app can be found in the [getting help section](https://mastering-shiny.org/action-workflow.html#getting-help) of [Mastering Shiny](https://mastering-shiny.org/index.html).
8 |
9 | ### Making Pull Requests
10 |
11 | Should you consider making a pull request (PR), please file an issue first and explain the problem or feature idea in some detail. If the PR is an enhancement, detail how the change would make things better for the general user base of the application.
12 |
13 |
--------------------------------------------------------------------------------
/README.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | output: github_document
3 | ---
4 |
5 |
6 |
7 | ```{r, include = FALSE}
8 | knitr::opts_chunk$set(
9 | collapse = TRUE,
10 | comment = "#>",
11 | fig.path = "man/figures/README-",
12 | out.width = "100%"
13 | )
14 | ```
15 |
16 | # The Data Science StreamRs Calendar!
17 |
18 |
19 |
20 |
21 | In the last few years, we have seen an huge uptick in the number of data science professionals and hobbyists who like to stream their projects and share their knowledge with the community! This calendar is my small attempt to contribute back to this awesome community so you can see what great streams are happening!
22 |
23 | This repository contains the Shiny app providing the streamers calendar, created with the [`{golem}`](https://golemverse.org/) package. In addition, this app would not be possible without the [`{toastui}`](https://github.com/dreamRs/toastui) package by the fine friends at DreamRs.
24 |
25 | ## Installation
26 |
27 | You can install the development version from [GitHub](https://github.com/) with:
28 |
29 | ``` r
30 | # install.packages("remotes")
31 | remotes::install_github("rpodcast/shinycal")
32 | ```
33 |
34 | ## Code of Conduct
35 |
36 | Please note that the shinycal project is released with a [Contributor Code of Conduct](https://contributor-covenant.org/version/2/0/CODE_OF_CONDUCT.html). By contributing to this project, you agree to abide by its terms.
--------------------------------------------------------------------------------
/NEWS.md:
--------------------------------------------------------------------------------
1 | # shinycal 0.8
2 |
3 | * Add a feedback Shiny module so users can easily report issues with using the app directly on GitHub via the `gh` package. Credit to @shannonpileggi for the idea!
4 |
5 | # shinycal 0.7
6 |
7 | * Add custom javascript for detecting the time zone of the user browsing the app, and make that the default time zone when application finishes loading. This solves the bug of time zones not being correctly mapped for those outside of the original America/New_York time zone (#26)
8 |
9 | # shinycal 0.6
10 |
11 | * Account for daylight savings time by adding `ambiguous` param to custom time parser functions
12 | * Add nodejs and puppeteer to the development contrainer for further timezone testing
13 |
14 | # shinycal 0.5.1
15 |
16 | * Organize the time zone choices into groups (thanks, @tanho63 and @PythonCoderUnicorn!)
17 | # shinycal 0.5
18 |
19 | * Add a new time zone selector (powered by `{shinyWidgets}`)
20 | * Revise how the view type changes the calendar view on the backend. The value of this input is now directly fed into the calendar function, instead of changing it via the proxy object.
21 | # shinycal 0.4
22 |
23 | * Move video player for a streamer's latest VOD to the right of the calendar
24 | # shinycal 0.3
25 |
26 | * Added a `NEWS.md` file to track changes to the package.
27 | * Allow user to change the calendar entry background color as well as font color
28 | * Add placeholder margin for new elements
29 | * Restructured reactive components of the calendar display data
30 |
--------------------------------------------------------------------------------
/testapp/app.R:
--------------------------------------------------------------------------------
1 | #
2 | # This is a Shiny web application. You can run the application by clicking
3 | # the 'Run App' button above.
4 | #
5 | # Find out more about building applications with Shiny here:
6 | #
7 | # http://shiny.rstudio.com/
8 | #
9 |
10 | library(shiny)
11 | library(toastui)
12 | library(calendar)
13 |
14 | ics_df <- ic_read("../r_code/wimpys-world-of-streamers-2021-06-08.ics")
15 |
16 |
17 | # Define UI for application that draws a histogram
18 | ui <- fluidPage(
19 |
20 | fluidRow(
21 | column(
22 | width = 12,
23 | calendarOutput("testout", width = "100%", height = "600px")
24 | )
25 | )
26 | )
27 |
28 | # Define server logic required to draw a histogram
29 | server <- function(input, output, session) {
30 |
31 | output$testout <- renderCalendar({
32 | calendar(cal_demo_data(), useNavigation = TRUE) %>%
33 | cal_props(
34 | list(
35 | id = 1,
36 | name = "PERSO",
37 | color = "white",
38 | bgColor = "firebrick",
39 | borderColor = "firebrick"
40 | ),
41 | list(
42 | id = 2,
43 | name = "WORK",
44 | color = "white",
45 | bgColor = "forestgreen",
46 | borderColor = "forestgreen"
47 | )
48 | )
49 | })
50 | }
51 |
52 | # Run the application
53 | shinyApp(ui = ui, server = server)
54 |
--------------------------------------------------------------------------------
/vignettes/github_backend.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "github_backend"
3 | output: rmarkdown::html_vignette
4 | vignette: >
5 | %\VignetteIndexEntry{github_backend}
6 | %\VignetteEngine{knitr::rmarkdown}
7 | %\VignetteEncoding{UTF-8}
8 | ---
9 |
10 | ```{r, include = FALSE}
11 | knitr::opts_chunk$set(
12 | collapse = TRUE,
13 | comment = "#>"
14 | )
15 | ```
16 |
17 | ```{r setup}
18 | library(usethis)
19 | ```
20 |
21 | ### Setting up a local copy of repo
22 |
23 | ```{r setup-git}
24 | github_url <- "git@github.com:rpodcast/streamer_data_dev.git"
25 |
26 | #dest_dir <- fs::path("prototyping", "github_data")
27 | dest_dir <- fs::path(fs::path_temp(), "github_data")
28 |
29 | if (fs::dir_exists(dest_dir)) fs::dir_delete(dest_dir)
30 | fs::dir_create(dest_dir)
31 |
32 | create_from_github(github_url, destdir = dest_dir, fork = FALSE, rstudio = FALSE, open = FALSE, protocol = "ssh")
33 |
34 | fs::dir_tree(dest_dir)
35 | ```
36 |
37 | ### Try sending a PR with a new file
38 |
39 | ```{r pr-from-local}
40 | library(gert)
41 | # make a new yaml file
42 | repo_dir <- fs::path(dest_dir, "streamer_data_dev")
43 | yaml_dir <- fs::path(dest_dir, "streamer_data_dev", "yaml_files")
44 |
45 | fs::dir_ls(yaml_dir)
46 | # copy one for testing
47 | fs::file_copy(fs::path(yaml_dir, "unicoRn_coder.yml"), fs::path(yaml_dir, "live_streamer.yml"))
48 |
49 | withr::with_dir(repo_dir, {
50 | git_add("yaml_files/live_streamer.yml")
51 | git_commit("Add live_streamer.yml")
52 | usethis::pr_init(branch = "live-streamer3")
53 | #usethis::pr_push()
54 | system("git push -u origin live-streamer3")
55 | })
56 |
57 |
58 |
59 |
60 | ```
61 |
--------------------------------------------------------------------------------
/man/run_app.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/run_app.R
3 | \name{run_app}
4 | \alias{run_app}
5 | \title{Run the Shiny Application}
6 | \usage{
7 | run_app(
8 | onStart = NULL,
9 | options = list(),
10 | enableBookmarking = NULL,
11 | uiPattern = "/",
12 | ...
13 | )
14 | }
15 | \arguments{
16 | \item{onStart}{A function that will be called before the app is actually run.
17 | This is only needed for \code{shinyAppObj}, since in the \code{shinyAppDir}
18 | case, a \code{global.R} file can be used for this purpose.}
19 |
20 | \item{options}{Named options that should be passed to the \code{runApp} call
21 | (these can be any of the following: "port", "launch.browser", "host", "quiet",
22 | "display.mode" and "test.mode"). You can also specify \code{width} and
23 | \code{height} parameters which provide a hint to the embedding environment
24 | about the ideal height/width for the app.}
25 |
26 | \item{enableBookmarking}{Can be one of \code{"url"}, \code{"server"}, or
27 | \code{"disable"}. The default value, \code{NULL}, will respect the setting from
28 | any previous calls to \code{\link[shiny:enableBookmarking]{enableBookmarking()}}. See \code{\link[shiny:enableBookmarking]{enableBookmarking()}}
29 | for more information on bookmarking your app.}
30 |
31 | \item{uiPattern}{A regular expression that will be applied to each \code{GET}
32 | request to determine whether the \code{ui} should be used to handle the
33 | request. Note that the entire request path must match the regular
34 | expression in order for the match to be considered successful.}
35 |
36 | \item{...}{arguments to pass to golem_opts.
37 | See `?golem::get_golem_options` for more details.}
38 | }
39 | \description{
40 | Run the Shiny Application
41 | }
42 |
--------------------------------------------------------------------------------
/dev/python_code/hello.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 | import sys
3 | import caldav
4 | import os
5 | from dotenv import load_dotenv
6 |
7 | #https://nextcloud.r-podcastdev.link/index.php/apps/calendar/p/zNyH2RE3pLHctoEf
8 |
9 | # "https://nextcloud03.webo.cloud/remote.php/dav/calendars/me@myweb.de/personal/"
10 | # "https://nextcloud.r-podcastdev.link/remote.php/dav/calendars/rpodcast/wimpys-world-of-streamers/"
11 | #caldav_url = 'https://calendar.example.com/dav'
12 | caldav_url = 'https://nextcloud.r-podcastdev.link/remote.php/dav'
13 |
14 | # load environment variables for user name and password to Nextcloud
15 | load_dotenv()
16 |
17 | client = caldav.DAVClient(url=caldav_url, username=os.getenv("NEXTCLOUD_USER"), password=os.getenv("NEXTCLOUD_PASSWORD"))
18 | my_principal = client.principal()
19 | calendars = my_principal.calendars()
20 |
21 | wimpy_cal = "https://nextcloud.r-podcastdev.link/remote.php/dav/calendars/rpodcast/wimpys-world-of-streamers/"
22 | test_cal = "https://nextcloud.r-podcastdev.link/remote.php/dav/calendars/rpodcast/erics-play-calendar/"
23 |
24 | the_same_calendar = client.calendar(url=wimpy_cal)
25 |
26 | all_events = the_same_calendar.events()
27 |
28 | if all_events:
29 | print("your calendar has %i cevents:" % len(all_events))
30 | for e in all_events:
31 | print(e)
32 | print(e.data)
33 | else:
34 | print("your calendar has no events")
35 |
36 |
37 | if calendars:
38 | ## Some calendar servers will include all calendars you have
39 | ## access to in this list, and not only the calendars owned by
40 | ## this principal.
41 | print("your principal has %i calendars:" % len(calendars))
42 | for c in calendars:
43 | print(" Name: %-20s URL: %s" % (c.name, c.url))
44 | else:
45 | print("your principal has no calendars")
46 |
47 |
--------------------------------------------------------------------------------
/dev/03_deploy.R:
--------------------------------------------------------------------------------
1 | # Building a Prod-Ready, Robust Shiny Application.
2 | #
3 | # README: each step of the dev files is optional, and you don't have to
4 | # fill every dev scripts before getting started.
5 | # 01_start.R should be filled at start.
6 | # 02_dev.R should be used to keep track of your development during the project.
7 | # 03_deploy.R should be used once you need to deploy your app.
8 | #
9 | #
10 | ######################################
11 | #### CURRENT FILE: DEPLOY SCRIPT #####
12 | ######################################
13 |
14 | # Test your app
15 |
16 | ## Run checks ----
17 | ## Check the package before sending to prod
18 | devtools::check()
19 | #rhub::check_for_cran()
20 |
21 | # Deploy
22 |
23 | ## Local, CRAN or Package Manager ----
24 | ## This will build a tar.gz that can be installed locally,
25 | ## sent to CRAN, or to a package manager
26 | devtools::build()
27 |
28 | ## RStudio ----
29 | ## If you want to deploy on RStudio related platforms
30 | golem::add_rstudioconnect_file()
31 | golem::add_shinyappsio_file()
32 | golem::add_shinyserver_file()
33 |
34 | ## Docker ----
35 | ## If you want to deploy via a generic Dockerfile
36 | golem::add_dockerfile()
37 |
38 | ## If you want to deploy to ShinyProxy
39 | golem::add_dockerfile_shinyproxy()
40 |
41 | ## If you want to deploy to Heroku
42 | golem::add_dockerfile_heroku()
43 |
44 | ## deploy app
45 | # if deploying in vs code container, must create /workspaces/rsconnect dir first
46 | # sudo mkdir /workspaces/rsconnect
47 | # sudo chown -R eric:eric /workspaces/rsconnect
48 |
49 | # one-time operation
50 | rsconnect::setAccountInfo(
51 | name = "rpodcast",
52 | token = Sys.getenv("RSCONNECT_TOKEN"),
53 | secret = Sys.getenv("RSCONNECT_SECRET")
54 | )
55 |
56 | library(gert)
57 | current_branch <- git_branch()
58 | app_name <- golem::get_golem_name()
59 |
60 | if (current_branch != "master") {
61 | app_name <- paste0(app_name, "_dev")
62 | }
63 |
64 | rsconnect::deployApp(
65 | appName = app_name,
66 | appFileManifest = "dev/app_manifest.txt",
67 | launch.browser = FALSE,
68 | forceUpdate = TRUE
69 | )
70 |
--------------------------------------------------------------------------------
/dev/hello.R:
--------------------------------------------------------------------------------
1 | library(purrr)
2 |
3 | # these should not be needed now that I set the python path env var via .Rprofile
4 | #reticulate::use_python("/usr/bin/python3")
5 | #reticulate::py_config()
6 | #reticulate::py_available()
7 |
8 | library(reticulate)
9 |
10 | caldav_url = 'https://nextcloud.r-podcastdev.link/remote.php/dav'
11 |
12 | caldav <- import("caldav")
13 |
14 | client <- caldav$DAVClient(url = caldav_url, username = Sys.getenv("NEXTCLOUD_USER"), password = Sys.getenv("NEXTCLOUD_PASSWORD"))
15 |
16 | my_principal <- client$principal()
17 | calendars <- my_principal$calendars()
18 |
19 | purrr::walk(calendars, ~{
20 | message(glue::glue("Name {.x$name} URL {.x$url}"))
21 | })
22 |
23 | per_cal_url <- "https://nextcloud.r-podcastdev.link/remote.php/dav/calendars/rpodcast/personal/"
24 | wimpy_cal_url <- "https://nextcloud.r-podcastdev.link/remote.php/dav/calendars/rpodcast/wimpys-world-of-streamers/"
25 | test_cal_url <- "https://nextcloud.r-podcastdev.link/remote.php/dav/calendars/rpodcast/erics-play-calendar/"
26 |
27 | wimpy_cal <- client$calendar(url = wimpy_cal_url)
28 | wimpy_events <- wimpy_cal$events()
29 |
30 | one_event <- wimpy_events[[1]]
31 |
32 |
33 | data_obj <- one_event$data
34 |
35 | one_event$get_property(prop = "DTSTART")
36 |
37 |
38 | library(calendar)
39 | ical_example = readLines(system.file("extdata", "example.ics", package = "calendar"))
40 |
41 | x <- ic_attributes_vec(x = ical_example)
42 |
43 | ics_df <- ic_read(system.file("extdata", "example.ics", package = "calendar"))
44 | ics_df <- ic_read("r_code/wimpys-world-of-streamers-2021-06-08.ics")
45 |
46 | library(caldav)
47 |
48 | caldav_url = "https://nextcloud.r-podcastdev.link/remote.php/dav/calendars/rpodcast/wimpys-world-of-streamers/"
49 | cal_data <-
50 | caldav_get_all_simple_auth(
51 | url = caldav_url,
52 | user = Sys.getenv("NEXTCLOUD_USER"),
53 | password = Sys.getenv("NEXTCLOUD_PASSWORD")
54 | )
55 |
56 | x <- cal_data$calendar
57 |
58 | cat(x, file = "r_code/tmp.ics")
59 |
60 |
61 | library(purrr)
62 | tidy_cal <- purrr::map(x, ~stringr::str_split(.x, "\\n"))
63 |
64 | ic_read("r_code/tmp.ics")
65 |
66 |
67 | tidy_cal[[1]]
68 |
--------------------------------------------------------------------------------
/dev/r_code/hello.R:
--------------------------------------------------------------------------------
1 | library(purrr)
2 |
3 | # these should not be needed now that I set the python path env var via .Rprofile
4 | #reticulate::use_python("/usr/bin/python3")
5 | #reticulate::py_config()
6 | #reticulate::py_available()
7 |
8 | library(reticulate)
9 |
10 | caldav_url = 'https://nextcloud.r-podcastdev.link/remote.php/dav'
11 |
12 | caldav <- import("caldav")
13 |
14 | client <- caldav$DAVClient(url = caldav_url, username = Sys.getenv("NEXTCLOUD_USER"), password = Sys.getenv("NEXTCLOUD_PASSWORD"))
15 |
16 | my_principal <- client$principal()
17 | calendars <- my_principal$calendars()
18 |
19 | purrr::walk(calendars, ~{
20 | message(glue::glue("Name {.x$name} URL {.x$url}"))
21 | })
22 |
23 | per_cal_url <- "https://nextcloud.r-podcastdev.link/remote.php/dav/calendars/rpodcast/personal/"
24 | wimpy_cal_url <- "https://nextcloud.r-podcastdev.link/remote.php/dav/calendars/rpodcast/wimpys-world-of-streamers/"
25 | test_cal_url <- "https://nextcloud.r-podcastdev.link/remote.php/dav/calendars/rpodcast/erics-play-calendar/"
26 |
27 | wimpy_cal <- client$calendar(url = wimpy_cal_url)
28 | wimpy_events <- wimpy_cal$events()
29 |
30 | one_event <- wimpy_events[[1]]
31 |
32 |
33 | data_obj <- one_event$data
34 |
35 | one_event$get_property(prop = "DTSTART")
36 |
37 |
38 | library(calendar)
39 | ical_example = readLines(system.file("extdata", "example.ics", package = "calendar"))
40 |
41 | x <- ic_attributes_vec(x = ical_example)
42 |
43 | ics_df <- ic_read(system.file("extdata", "example.ics", package = "calendar"))
44 | ics_df <- ic_read("r_code/wimpys-world-of-streamers-2021-06-08.ics")
45 |
46 | library(caldav)
47 |
48 | caldav_url = "https://nextcloud.r-podcastdev.link/remote.php/dav/calendars/rpodcast/wimpys-world-of-streamers/"
49 | cal_data <-
50 | caldav_get_all_simple_auth(
51 | url = caldav_url,
52 | user = Sys.getenv("NEXTCLOUD_USER"),
53 | password = Sys.getenv("NEXTCLOUD_PASSWORD")
54 | )
55 |
56 | x <- cal_data$calendar
57 |
58 | cat(x, file = "r_code/tmp.ics")
59 |
60 |
61 | library(purrr)
62 | tidy_cal <- purrr::map(x, ~stringr::str_split(.x, "\\n"))
63 |
64 | ic_read("r_code/tmp.ics")
65 |
66 |
67 | tidy_cal[[1]]
68 |
--------------------------------------------------------------------------------
/R/app_ui.R:
--------------------------------------------------------------------------------
1 | #' The application User-Interface
2 | #'
3 | #' @param request Internal parameter for `{shiny}`.
4 | #' DO NOT REMOVE.
5 | #' @import shiny
6 | #' @noRd
7 | app_ui <- function(request) {
8 | tagList(
9 | # Leave this function for adding external resources
10 | golem_add_external_resources(),
11 | # Your application UI logic
12 | navbarPage(
13 | title = "shinycal",
14 | tabPanel(
15 | title = "Calendar",
16 | value = "calendar",
17 | HTML(' '),
18 | HTML(' '),
19 | HTML(' '),
20 | fluidRow(
21 | col_12(
22 | h1("The Data Science StreamRs Calendar!"),
23 | shiny::includeMarkdown(app_sys("app", "www", "intro.md")),
24 | )
25 | ),
26 | fluidRow(
27 | col_12(
28 | mod_cal_viewer_ui("cal_viewer_ui_1")
29 | )
30 | )
31 | ),
32 | tabPanel(
33 | title = "Entry",
34 | value = "entry",
35 | h1("Under Construction!")
36 | #mod_cal_entry_ui("cal_entry_ui_1")
37 | ),
38 | tabPanel(
39 | title = "Feedback",
40 | value = "feedback",
41 | mod_feedback_ui("feedback_ui_1")
42 | )
43 | )
44 | )
45 | }
46 |
47 | #' Add external Resources to the Application
48 | #'
49 | #' This function is internally used to add external
50 | #' resources inside the Shiny application.
51 | #'
52 | #' @import shiny
53 | #' @importFrom golem add_resource_path activate_js favicon bundle_resources
54 | #' @noRd
55 | golem_add_external_resources <- function(){
56 |
57 | add_resource_path(
58 | 'www', app_sys('app/www')
59 | )
60 |
61 | tags$head(
62 | favicon(),
63 | bundle_resources(
64 | path = app_sys('app/www'),
65 | app_title = 'shinycal'
66 | ),
67 | tags$script(src="https://player.twitch.tv/js/embed/v1.js")
68 | # Add here other external resources
69 | # for example, you can add shinyalert::useShinyalert()
70 | )
71 | }
72 |
73 |
--------------------------------------------------------------------------------
/.Rprofile:
--------------------------------------------------------------------------------
1 |
2 | # setup if using with vscode and R plugin
3 | if (Sys.getenv("TERM_PROGRAM") == "vscode") {
4 | source(file.path(Sys.getenv(if (.Platform$OS.type == "windows") "USERPROFILE" else "HOME"), ".vscode-R", "init.R"))
5 | }
6 | source("renv/activate.R")
7 |
8 | if (Sys.getenv("TERM_PROGRAM") == "vscode") {
9 | # obtain list of packages in renv library currently
10 | project <- renv:::renv_project_resolve(NULL)
11 | lib_packages <- names(unclass(renv:::renv_diagnostics_packages_library(project))$Packages)
12 |
13 | # detect whether key packages are already installed
14 | # was: !require("languageserver")
15 | if (!"languageserver" %in% lib_packages) {
16 | message("installing languageserver package")
17 | renv::install("languageserver")
18 | }
19 |
20 | if (!"httpgd" %in% lib_packages) {
21 | message("installing httpgd package")
22 | renv::install("httpgd")
23 | }
24 |
25 | if (!"vscDebugger" %in% lib_packages) {
26 | message("installation vscDebugger package")
27 | renv::install("ManuelHentschel/vscDebugger@v0.4.7")
28 | }
29 |
30 | # use the rstudio addins feature
31 | if (!"rstudioapi" %in% lib_packages) {
32 | message("installing rstudioapi package")
33 | renv::install("rstudioapi")
34 | }
35 | options(vsc.rstudioapi = TRUE)
36 |
37 | # use the new httpgd plotting device
38 | options(vsc.plot = FALSE)
39 | options(device = function(...) {
40 | httpgd:::hgd()
41 | .vsc.browser(httpgd::hgd_url(), viewer = "Beside")
42 | })
43 | }
44 |
45 | # uncomment this section if using a pulseaudio server from a linux host
46 | play_sound <- function(sound_dir = "/soundboard_files", custom_sink = "SoundBoard", obs_animate = TRUE, wait = FALSE) {
47 | audio_file <- sample(list.files(sound_dir, full.names = TRUE), size = 1)
48 | play_args <- c(audio_file)
49 |
50 | if (!is.null(custom_sink)) {
51 | play_args <- c(play_args, "-d", custom_sink)
52 | }
53 |
54 | system2("paplay", args = play_args, wait = wait)
55 |
56 | if (obs_animate) {
57 | system2("curl", paste0("http://192.168.1.178:1030/image?filename=/", audio_file), stdout = FALSE, stderr = FALSE)
58 | }
59 |
60 | invisible(TRUE)
61 | }
62 |
63 | options(error = play_sound)
--------------------------------------------------------------------------------
/dev/01_start.R:
--------------------------------------------------------------------------------
1 | # Building a Prod-Ready, Robust Shiny Application.
2 | #
3 | # README: each step of the dev files is optional, and you don't have to
4 | # fill every dev scripts before getting started.
5 | # 01_start.R should be filled at start.
6 | # 02_dev.R should be used to keep track of your development during the project.
7 | # 03_deploy.R should be used once you need to deploy your app.
8 | #
9 | #
10 | ########################################
11 | #### CURRENT FILE: ON START SCRIPT #####
12 | ########################################
13 |
14 | ## Fill the DESCRIPTION ----
15 | ## Add meta data about your application
16 | ##
17 | ## /!\ Note: if you want to change the name of your app during development,
18 | ## either re-run this function, call golem::set_golem_name(), or don't forget
19 | ## to change the name in the app_sys() function in app_config.R /!\
20 | ##
21 | golem::fill_desc(
22 | pkg_name = "shinycal", # The Name of the package containing the App
23 | pkg_title = "PKG_TITLE", # The Title of the package containing the App
24 | pkg_description = "PKG_DESC.", # The Description of the package containing the App
25 | author_first_name = "AUTHOR_FIRST", # Your First Name
26 | author_last_name = "AUTHOR_LAST", # Your Last Name
27 | author_email = "AUTHOR@MAIL.COM", # Your Email
28 | repo_url = "https://github.com/rpodcast/shinycal" # The URL of the GitHub Repo (optional)
29 | )
30 |
31 | ## Set {golem} options ----
32 | golem::set_golem_options()
33 |
34 | ## Create Common Files ----
35 | ## See ?usethis for more information
36 | usethis::use_mit_license( "Eric Nantz" ) # You can set another license here
37 | usethis::use_readme_rmd( open = FALSE )
38 | usethis::use_code_of_conduct()
39 | usethis::use_lifecycle_badge( "Experimental" )
40 | usethis::use_news_md( open = FALSE )
41 |
42 | ## Use git ----
43 | usethis::use_git()
44 |
45 | ## Init Testing Infrastructure ----
46 | ## Create a template for tests
47 | golem::use_recommended_tests()
48 |
49 | ## Use Recommended Packages ----
50 | golem::use_recommended_deps()
51 |
52 | ## Favicon ----
53 | # If you want to change the favicon (default is golem's one)
54 | golem::use_favicon() # path = "path/to/ico". Can be an online file.
55 | golem::remove_favicon()
56 |
57 | ## Add helper functions ----
58 | golem::use_utils_ui()
59 | golem::use_utils_server()
60 |
61 | # You're now set! ----
62 |
63 | # go to dev/02_dev.R
64 | rstudioapi::navigateToFile( "dev/02_dev.R" )
65 |
66 |
--------------------------------------------------------------------------------
/.devcontainer/rstudio_docker/Dockerfile:
--------------------------------------------------------------------------------
1 | ##################################################################################################################################################################
2 | # Adapted from following:
3 | # - Rocker RStudio container using new versioned paradigm: https://github.com/rocker-org/rocker-versioned2/blob/master/dockerfiles/Dockerfile_rstudio_4.0.2
4 | # - license: GPLV2
5 | ##################################################################################################################################################################
6 |
7 | FROM rocker/r-ver:4.1.0
8 |
9 | ENV S6_VERSION=v1.21.7.0
10 | ENV RSTUDIO_VERSION=preview
11 | ENV PATH=/usr/lib/rstudio-server/bin:$PATH
12 |
13 | # install key dependencies of rstudio preview edition
14 | # install radian via python and pip3
15 | RUN apt-get update \
16 | && export DEBIAN_FRONTEND=noninteractive \
17 | && apt-get -y install --no-install-recommends libpq5 openssh-client openssh-server
18 |
19 | RUN /rocker_scripts/install_rstudio.sh
20 | RUN /rocker_scripts/install_pandoc.sh
21 |
22 | # install key dependencies of certain packages that could be installed later
23 | RUN apt-get update \
24 | && export DEBIAN_FRONTEND=noninteractive \
25 | && apt-get -y install --no-install-recommends software-properties-common libssl-dev libxml2-dev libcurl4-openssl-dev python3-pip pandoc \
26 | && apt-get -y install --no-install-recommends libfreetype6-dev libfontconfig1-dev tk libgit2-dev \
27 | && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/*
28 |
29 | # install R packages needed for VSCode interaction and package management
30 | RUN install2.r languageserver renv remotes reticulate golem crul curl httr xml2 cachem shinyWidgets dplyr purrr ggplot2 tidyr fs glue calendar devtools rmarkdown clock
31 | RUN installGithub.r dreamRs/toastui ColinFay/brochure ColinFay/glouton petermeissner/caldav koderkow/twitchr
32 |
33 | # install radian via python and pip3
34 | RUN apt-get update \
35 | && export DEBIAN_FRONTEND=noninteractive \
36 | && apt-get -y install --no-install-recommends python3-setuptools python3-pip libpython3-dev
37 |
38 | RUN pip3 install radian caldav icalendar numpy
39 |
40 | RUN echo "RENV_PATHS_CACHE=/renv/cache" >> /usr/local/lib/R/etc/Renviron
41 |
42 | # [Optional] Uncomment this section to add addtional system dependencies needed for project
43 | # RUN apt-get update \
44 | # && export DEBIAN_FRONTEND=noninteractive \
45 | # && apt-get -y install --no-install-recommends ---packages list----
46 |
47 | EXPOSE 8787
48 |
49 | CMD ["/init"]
50 |
--------------------------------------------------------------------------------
/dev/02_dev.R:
--------------------------------------------------------------------------------
1 | # Building a Prod-Ready, Robust Shiny Application.
2 | #
3 | # README: each step of the dev files is optional, and you don't have to
4 | # fill every dev scripts before getting started.
5 | # 01_start.R should be filled at start.
6 | # 02_dev.R should be used to keep track of your development during the project.
7 | # 03_deploy.R should be used once you need to deploy your app.
8 | #
9 | #
10 | ###################################
11 | #### CURRENT FILE: DEV SCRIPT #####
12 | ###################################
13 |
14 | # Engineering
15 |
16 | ## Dependencies ----
17 | ## Add one line by package you want to add as dependency
18 | usethis::use_package("calendar")
19 | usethis::use_dev_package("petermeissner/caldav")
20 | usethis::use_dev_package("dreamRs/toastui")
21 | usethis::use_package("purrr")
22 | usethis::use_package("dplyr")
23 |
24 |
25 | ## Add modules ----
26 | ## Create a module infrastructure in R/
27 | golem::add_module( name = "cal_viewer" ) # Name of the module
28 | golem::add_module(name = "cal_entry")
29 |
30 | ## Add helper functions ----
31 | ## Creates fct_* and utils_*
32 | golem::add_fct( "helpers", module = "cal_viewer")
33 | golem::add_utils( "helpers" )
34 |
35 | ## External resources
36 | ## Creates .js and .css files at inst/app/www
37 | golem::add_js_file( "script" )
38 | golem::add_js_handler( "handlers" )
39 | golem::add_css_file( "custom" )
40 |
41 | ## Add internal datasets ----
42 | ## If you have data in your package
43 | usethis::use_data_raw( name = "streamer_data", open = FALSE )
44 |
45 | ## Tests ----
46 | ## Add one line by test you want to create
47 | usethis::use_test( "app" )
48 |
49 | # Documentation
50 |
51 | ## Vignette ----
52 | usethis::use_vignette("tidying_calendar")
53 | usethis::use_vignette("streamer_info")
54 |
55 | devtools::build_vignettes()
56 |
57 | ## Code Coverage----
58 | ## Set the code coverage service ("codecov" or "coveralls")
59 | usethis::use_coverage()
60 |
61 | # Create a summary readme for the testthat subdirectory
62 | covrpage::covrpage()
63 |
64 | ## CI ----
65 | ## Use this part of the script if you need to set up a CI
66 | ## service for your application
67 | ##
68 | ## (You'll need GitHub there)
69 | usethis::use_github()
70 |
71 | # GitHub Actions
72 | usethis::use_github_action()
73 | # Chose one of the three
74 | # See https://usethis.r-lib.org/reference/use_github_action.html
75 | usethis::use_github_action_check_release()
76 | usethis::use_github_action_check_standard()
77 | usethis::use_github_action_check_full()
78 | # Add action for PR
79 | usethis::use_github_action_pr_commands()
80 |
81 | # Travis CI
82 | usethis::use_travis()
83 | usethis::use_travis_badge()
84 |
85 | # AppVeyor
86 | usethis::use_appveyor()
87 | usethis::use_appveyor_badge()
88 |
89 | # Circle CI
90 | usethis::use_circleci()
91 | usethis::use_circleci_badge()
92 |
93 | # Jenkins
94 | usethis::use_jenkins()
95 |
96 | # GitLab CI
97 | usethis::use_gitlab_ci()
98 |
99 | # You're now set! ----
100 | # go to dev/03_deploy.R
101 | rstudioapi::navigateToFile("dev/03_deploy.R")
102 |
103 |
--------------------------------------------------------------------------------
/R/mod_cal_entry.R:
--------------------------------------------------------------------------------
1 | #' cal_entry UI Function
2 | #'
3 | #' @description A shiny Module.
4 | #'
5 | #' @param id,input,output,session Internal parameters for {shiny}.
6 | #'
7 | #' @noRd
8 | #'
9 | #' @importFrom shiny NS tagList
10 | #' @import shinyvalidate
11 | mod_cal_entry_ui <- function(id){
12 | ns <- NS(id)
13 | tagList(
14 | fluidRow(
15 | col_6(
16 | textInput(
17 | ns("user_id"),
18 | with_red_star("Twitch user ID"),
19 | value = "",
20 | placeholder = "Please enter an ID"
21 | ),
22 | textInput(
23 | ns("user_name"),
24 | with_red_star("Enter name"),
25 | value = ""
26 | ),
27 | textInput(
28 | ns("twitter_id"),
29 | "Twitter ID (Optional)",
30 | value = ""
31 | ),
32 | shinyWidgets::prettyRadioButtons(
33 | ns("platform"),
34 | label = "Choose Streaming Platform",
35 | choices = c("twitch", "youtube"),
36 | selected = "twitch"
37 | ),
38 | shinyWidgets::pickerInput(
39 | ns("categories"),
40 | label = with_red_star("Select categories"),
41 | choices = c("R", "datascience", "shiny", "sports", "gaming", "linux", "julia", "python"),
42 | selected = "R",
43 | options = list(
44 | `actions-box` = TRUE),
45 | multiple = TRUE
46 | ),
47 | shinyWidgets::actionBttn(
48 | ns("submit"),
49 | label = "Submit!",
50 | icon = "rocket",
51 | style = "jelly",
52 | color = "success",
53 | size = "lg"
54 | ),
55 | verbatimTextOutput(
56 | ns("debugging")
57 | )
58 | )
59 | )
60 | )
61 | }
62 |
63 | #' cal_entry Server Functions
64 | #'
65 | #' @noRd
66 | mod_cal_entry_server <- function(id){
67 | moduleServer( id, function(input, output, session){
68 | ns <- session$ns
69 |
70 | # reactive vals
71 | entry_yaml <- reactiveVal(NULL)
72 |
73 | # establish form validation processing and rules
74 | iv <- InputValidator$new()
75 |
76 | iv$add_rule("user_id", sv_required())
77 | iv$add_rule("user_name", sv_required())
78 | iv$add_rule("categories", sv_required())
79 |
80 | # process form submission
81 | observeEvent(input$submit, {
82 | # ensure form is valid before processing
83 | iv$enable()
84 |
85 | if (!iv$is_valid()) {
86 | shinyWidgets::show_alert(
87 | title = "Oops",
88 | text = "Please complete the form"
89 | )
90 | return(NULL)
91 | }
92 |
93 | req(iv$is_valid())
94 |
95 | # assemble form inputs and create yaml structure
96 | entry_list <- list(
97 | user_id = input$user_id,
98 | user_name = input$user_name,
99 | twitter_id = input$twitter_id,
100 | platform = input$platform,
101 | categories = input$categories
102 | )
103 |
104 | en_yaml <- yaml::as.yaml(entry_list)
105 | entry_yaml(en_yaml)
106 | })
107 |
108 | output$debugging <- renderPrint({
109 | req(entry_yaml())
110 | cat(entry_yaml())
111 | })
112 |
113 | })
114 | }
115 |
116 | ## To be copied in the UI
117 | # mod_cal_entry_ui("cal_entry_ui_1")
118 |
119 | ## To be copied in the server
120 | # mod_cal_entry_server("cal_entry_ui_1")
121 |
--------------------------------------------------------------------------------
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | // For format details, see https://aka.ms/vscode-remote/devcontainer.json or this file's README at:
2 | // https://github.com/microsoft/vscode-dev-containers/tree/v0.134.1/containers/r
3 | {
4 | "name": "R",
5 | "build": {
6 | "dockerfile": "Dockerfile",
7 | "args": {
8 | "USERNAME": "${localEnv:USER}"
9 | }
10 | },
11 | "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined", "--env-file", ".devcontainer/.env" ],
12 |
13 | // Add more local mounts to container
14 | // The first mounts are for a local pulseaudio server on linux
15 | // You can leave these commented out or delete if you are not linking the contianer to a linux sound system
16 | "mounts": [
17 | "source=/etc/alsa,target=/etc/alsa,type=bind,consistency=cached",
18 | "source=/usr/share/alsa,target=/usr/share/alsa,type=bind,consistency=cached",
19 | "source=/run/user/1000/pulse/native,target=/run/user/1000/pulse/native,type=bind,consistency=cached",
20 | "source=/media/media_drive1/obs_files/audio_assets/soundboard_files,target=/soundboard_files,type=bind,consistency=cached",
21 | "source=/opt/local/renv/cache,target=/renv/cache,type=bind,consistency=cached"
22 | ],
23 | // Configure environment variables for audio sound server if needed
24 | // You can leave these commented out or delete if you are not linking the contianer to a linux sound system
25 | "containerEnv": {
26 | "PULSE_SERVER": "unix:/run/user/1000/pulse/native"
27 | },
28 |
29 | // Set *default* container specific settings.json values on container create.
30 | "settings": {
31 | "terminal.integrated.profiles.linux": {
32 | "fish": {
33 | "path": "/usr/bin/fish"
34 | }
35 | },
36 | "terminal.integrated.defaultProfile.linux": "fish",
37 | "r.alwaysUseActiveTerminal": true,
38 | "r.bracketedPaste": true,
39 | "r.sessionWatcher": true,
40 | "r.rterm.linux": "/usr/local/bin/radian",
41 | "r.rterm.option": [
42 | ""
43 | ],
44 | "r.autoDetect": "false",
45 | "r.terminalPath": "/usr/local/bin/radian",
46 | "r.interpreterPath": "/usr/local/bin/R",
47 | "r.debugger.timeouts.startup": 8000,
48 | "editor.wordWrap": "on",
49 | "editor.tabSize": 2,
50 | "path-autocomplete.pathMappings": {
51 | "/": "/",
52 | "./": "${folder}"
53 | },
54 | "editor.bracketPairColorization.enabled": true,
55 | "editor.guides.bracketPairs": "active"
56 | },
57 |
58 | // Add the IDs of extensions you want installed when the container is created.
59 | "extensions": [
60 | "ikuyadeu.r",
61 | //"/renv/cache/r-latest.vslx",
62 | "ionutvmi.path-autocomplete",
63 | "usernamehw.errorlens",
64 | "mhutchie.git-graph",
65 | "wayou.vscode-todo-highlight",
66 | "tomoki1207.pdf",
67 | "DavidAnson.vscode-markdownlint",
68 | "Rubymaniac.vscode-paste-and-indent",
69 | "GrapeCity.gc-excelviewer",
70 | "IBM.output-colorizer",
71 | "Mohamed-El-Fodil-Ihaddaden.shinysnip",
72 | "hediet.vscode-drawio",
73 | "MS-vsliveshare.vsliveshare-pack",
74 | "ms-python.python",
75 | "RDebugger.r-debugger",
76 | //"GitHub.copilot",
77 | "eamodio.gitlens",
78 | "GitHub.vscode-pull-request-github",
79 | "hoovercj.vscode-power-mode"
80 | ]
81 |
82 | // Use 'forwardPorts' to make a list of ports inside the container available locally.
83 | // "forwardPorts": [],
84 |
85 | // Use 'postCreateCommand' to run commands after the container is created.
86 | // "postCreateCommand": "R --version",
87 |
88 | // Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root.
89 | // "remoteUser": "docker"
90 | }
91 |
--------------------------------------------------------------------------------
/R/mod_feedback.R:
--------------------------------------------------------------------------------
1 | #' feedback UI Function
2 | #'
3 | #' @description A shiny Module.
4 | #'
5 | #' @param id,input,output,session Internal parameters for {shiny}.
6 | #'
7 | #' @noRd
8 | #'
9 | #' @importFrom shiny NS tagList
10 | mod_feedback_ui <- function(id){
11 | ns <- NS(id)
12 | tagList(
13 | fluidRow(
14 | col_12(
15 | h2("Submit Feedback!"),
16 | p("Use the form below to provide feedback to the author of shinycal directly on the issue tracker.")
17 | )
18 | ),
19 | fluidRow(
20 | col_6(
21 | textInput(
22 | ns("issue_title"),
23 | with_red_star("Title"),
24 | value = "",
25 | placeholder = "enter short title",
26 | width = "100%"
27 | )
28 | ),
29 | col_6(
30 | shinyWidgets::pickerInput(
31 | ns("issue_labels"),
32 | label = with_red_star("Choose one or more labels"),
33 | choices = c("silly"),
34 | multiple = TRUE
35 | )
36 | )
37 | ),
38 | fluidRow(
39 | col_12(
40 | textAreaInput(
41 | ns("issue_description"),
42 | label = with_red_star("Enter issue description"),
43 | value = "",
44 | width = "300%",
45 | cols = 80,
46 | rows = 10,
47 | placeholder = "markdown format supported",
48 | resize = "vertical"
49 | )
50 | )
51 | ),
52 | fluidRow(
53 | col_2(
54 | shinyWidgets::actionBttn(
55 | ns("submit_issue"),
56 | "Submit!",
57 | icon = icon("save"),
58 | style = "jelly",
59 | color = "success",
60 | size = "sm"
61 | )
62 | )
63 | )
64 | )
65 | }
66 |
67 | #' feedback Server Functions
68 | #'
69 | #' @noRd
70 | mod_feedback_server <- function(id){
71 | moduleServer( id, function(input, output, session){
72 | ns <- session$ns
73 |
74 | # update issue label choices
75 | shinyWidgets::updatePickerInput(
76 | session,
77 | "issue_labels",
78 | choices = get_issue_labels(),
79 | selected = NULL
80 | )
81 |
82 | # process submit click
83 | observeEvent(input$submit_issue, {
84 | # perform mandatory input checks
85 | if (!shiny::isTruthy(input$issue_title)) {
86 | shinyWidgets::sendSweetAlert(
87 | session = session,
88 | title = "Oops!",
89 | text = "Please enter a title for your issue",
90 | type = "error"
91 | )
92 |
93 | return(NULL)
94 | }
95 |
96 | if (!shiny::isTruthy(input$issue_labels)) {
97 | shinyWidgets::sendSweetAlert(
98 | session = session,
99 | title = "Oops!",
100 | text = "Please choose at least one label",
101 | type = "error"
102 | )
103 |
104 | return(NULL)
105 | }
106 |
107 | if (!shiny::isTruthy(input$issue_description)) {
108 | shinyWidgets::sendSweetAlert(
109 | session = session,
110 | title = "Oops!",
111 | text = "Please enter an issue description",
112 | type = "error"
113 | )
114 |
115 | return(NULL)
116 | }
117 |
118 | # process submit
119 | res <- submit_issue(
120 | input$issue_title,
121 | input$issue_labels,
122 | input$issue_description
123 | )
124 |
125 | # show confirmation
126 | shinyWidgets::sendSweetAlert(
127 | session = session,
128 | title = "Issue submitted!",
129 | text = "Thank you for your feedback on shinycal! Your issue has been submitted to the shinycal GitHub issue tracker.",
130 | type = "success"
131 | )
132 |
133 | # reset the form inputs
134 | updateTextInput(
135 | session,
136 | "issue_title",
137 | value = ""
138 | )
139 |
140 | updateTextAreaInput(
141 | session,
142 | "issue_description",
143 | value = ""
144 | )
145 |
146 | shinyWidgets::updatePickerInput(
147 | session,
148 | "issue_labels",
149 | selected = NULL
150 | )
151 | })
152 | })
153 | }
154 |
155 | ## To be copied in the UI
156 | # mod_feedback_ui("feedback_ui_1")
157 |
158 | ## To be copied in the server
159 | # mod_feedback_server("feedback_ui_1")
160 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity and
10 | orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the overall
26 | community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards
42 | of acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies
54 | when an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail
56 | address, posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at [INSERT CONTACT
63 | METHOD]. All complaints will be reviewed and investigated promptly and fairly.
64 |
65 | All community leaders are obligated to respect the privacy and security of the
66 | reporter of any incident.
67 |
68 | ## Enforcement Guidelines
69 |
70 | Community leaders will follow these Community Impact Guidelines in determining
71 | the consequences for any action they deem in violation of this Code of Conduct:
72 |
73 | ### 1. Correction
74 |
75 | **Community Impact**: Use of inappropriate language or other behavior deemed
76 | unprofessional or unwelcome in the community.
77 |
78 | **Consequence**: A private, written warning from community leaders, providing
79 | clarity around the nature of the violation and an explanation of why the
80 | behavior was inappropriate. A public apology may be requested.
81 |
82 | ### 2. Warning
83 |
84 | **Community Impact**: A violation through a single incident or series of
85 | actions.
86 |
87 | **Consequence**: A warning with consequences for continued behavior. No
88 | interaction with the people involved, including unsolicited interaction with
89 | those enforcing the Code of Conduct, for a specified period of time. This
90 | includes avoiding interactions in community spaces as well as external channels
91 | like social media. Violating these terms may lead to a temporary or permanent
92 | ban.
93 |
94 | ### 3. Temporary Ban
95 |
96 | **Community Impact**: A serious violation of community standards, including
97 | sustained inappropriate behavior.
98 |
99 | **Consequence**: A temporary ban from any sort of interaction or public
100 | communication with the community for a specified period of time. No public or
101 | private interaction with the people involved, including unsolicited interaction
102 | with those enforcing the Code of Conduct, is allowed during this period.
103 | Violating these terms may lead to a permanent ban.
104 |
105 | ### 4. Permanent Ban
106 |
107 | **Community Impact**: Demonstrating a pattern of violation of community
108 | standards, including sustained inappropriate behavior, harassment of an
109 | individual, or aggression toward or disparagement of classes of individuals.
110 |
111 | **Consequence**: A permanent ban from any sort of public interaction within the
112 | community.
113 |
114 | ## Attribution
115 |
116 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
117 | version 2.0,
118 | available at https://www.contributor-covenant.org/version/2/0/
119 | code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at https://
128 | www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/.devcontainer/Dockerfile:
--------------------------------------------------------------------------------
1 | ###################################################################################################################
2 | # Adapted from https://github.com/microsoft/vscode-dev-containers/blob/master/containers/r/.devcontainer/Dockerfile
3 | # licence: MIT
4 | ###################################################################################################################
5 |
6 | FROM rocker/r-ver:4.1.0
7 |
8 | # Options for setup script
9 | ARG INSTALL_ZSH="false"
10 | ARG INSTALL_FISH="true"
11 | ARG UPGRADE_PACKAGES="true"
12 | ARG USERNAME=eric
13 | ARG USER_UID=1000
14 | ARG USER_GID=$USER_UID
15 |
16 | # Remove the docker user since I don't need it for VSCode containers
17 | #RUN groupmod docker --gid 1005 \
18 | # && usermod --uid 1005 docker
19 |
20 | # Install needed packages and setup non-root user. Use a separate RUN statement to add your own dependencies.
21 | COPY library-scripts/*.sh /tmp/library-scripts/
22 | RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
23 | && /bin/bash /tmp/library-scripts/common-debian.sh "${INSTALL_ZSH}" "${INSTALL_FISH}" "${USERNAME}" "${USER_UID}" "${USER_GID}" "${UPGRADE_PACKAGES}" \
24 | && apt-get -y install libzip-dev \
25 | && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/*
26 |
27 | # [Optional] Uncomment this section to install additional OS packages.
28 | # key dependencies for certain R packages
29 | RUN apt-get update \
30 | && export DEBIAN_FRONTEND=noninteractive \
31 | && apt-get -y install --no-install-recommends software-properties-common libssl-dev libxml2-dev libcurl4-openssl-dev python3-pip pandoc \
32 | && apt-get -y install --no-install-recommends libfreetype6-dev libfontconfig1-dev tk libgit2-dev \
33 | && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/*
34 |
35 | # install R packages needed for VSCode interaction and package management
36 | RUN install2.r languageserver renv remotes reticulate golem crul curl httr xml2 cachem shinyWidgets dplyr purrr ggplot2 tidyr fs glue calendar devtools rmarkdown clock
37 | RUN installGithub.r dreamRs/toastui ColinFay/brochure ColinFay/glouton petermeissner/caldav koderkow/twitchr
38 |
39 | # install radian via python and pip3
40 | RUN apt-get update \
41 | && export DEBIAN_FRONTEND=noninteractive \
42 | && apt-get -y install --no-install-recommends python3-setuptools
43 |
44 | RUN pip3 install radian caldav icalendar numpy python-dotenv
45 |
46 | # install dot net core runtime for the R Tools plugin
47 | RUN wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O /tmp/packages-microsoft-prod.deb \
48 | && dpkg -i /tmp/packages-microsoft-prod.deb
49 |
50 | RUN apt-get update \
51 | && apt-get -y install --no-install-recommends apt-transport-https \
52 | && apt-get -y install --no-install-recommends dotnet-sdk-3.1
53 |
54 | # ensure that the renv package cache env var is accessible in default R installation
55 | RUN echo "RENV_PATHS_CACHE=/renv/cache" >> /usr/local/lib/R/etc/Renviron
56 |
57 | # copy the modified .Rprofile template to the renv cache
58 | COPY library-scripts/.Rprofile-vscode /renv/.Rprofile-vscode
59 |
60 | # [Optional] Uncomment this section to add addtional system dependencies needed for project
61 |
62 | # [Optional] Uncomment this section to install additional OS packages.
63 | RUN apt-get update \
64 | && export DEBIAN_FRONTEND=noninteractive \
65 | && apt-get -y install --no-install-recommends \
66 | fonts-liberation \
67 | gconf-service \
68 | libxshmfence1 \
69 | libasound2 \
70 | libatk1.0-0 \
71 | libcairo2 \
72 | libcups2 \
73 | libfontconfig1 \
74 | libgbm-dev \
75 | libgdk-pixbuf2.0-0 \
76 | libgtk-3-0 \
77 | libicu-dev \
78 | libjpeg-dev \
79 | libnspr4 \
80 | libnss3 \
81 | libpango-1.0-0 \
82 | libpangocairo-1.0-0 \
83 | libpng-dev \
84 | libx11-6 \
85 | libx11-xcb1 \
86 | libxcb1 \
87 | libxcomposite1 \
88 | libxcursor1 \
89 | libxdamage1 \
90 | libxext6 \
91 | libxfixes3 \
92 | libxi6 \
93 | libxrandr2 \
94 | libxrender1 \
95 | libxss1 \
96 | libxtst6 \
97 | xdg-utils
98 |
99 | RUN curl -sL https://deb.nodesource.com/setup_16.x -o /tmp/nodesource_setup.sh
100 | RUN bash /tmp/nodesource_setup.sh
101 | RUN apt-get update \
102 | && export DEBIAN_FRONTEND=noninteractive \
103 | && apt-get -y install --no-install-recommends nodejs
104 |
105 | RUN npm install puppeteer -g
106 |
107 | # [Optional] Uncomment this section to add Hugo to the container
108 | # Customize version number as appropriate
109 | #RUN curl -L https://github.com/gohugoio/hugo/releases/download/v0.87.0/hugo_extended_0.87.0_Linux-64bit.deb -o /tmp/hugo.deb
110 | #RUN apt-get -y install ./tmp/hugo.deb
111 |
112 | # [Optional] Uncomment this section to add addtional system dependencies needed for project
113 | # RUN apt-get update \
114 | # && export DEBIAN_FRONTEND=noninteractive \
115 | # && apt-get -y install --no-install-recommends ---packages list----
116 |
117 | # [Optional] Uncomment this section for linking a local pusleaudio sound system on a linux host
118 | # to the container.
119 | RUN apt-get update \
120 | && export DEBIAN_FRONTEND=noninteractive \
121 | && apt-get -y install --no-install-recommends libpulse0 libasound2 libasound2-plugins pulseaudio-utils
122 |
123 | # install obs-cli (Go version)
124 | # https://github.com/muesli/obs-cli/releases
125 | ADD https://github.com/muesli/obs-cli/releases/download/v0.4.0/obs-cli_0.4.0_linux_x86_64.tar.gz /obs-cli-linux.tar.gz
126 | RUN tar -zxvf obs-cli-linux.tar.gz
127 | RUN cp /obs-cli /usr/local/bin/obs-cli
128 | RUN chmod 755 /usr/local/bin/obs-cli
129 |
130 | # install obs-cli (Javascript version)
131 | # https://github.com/leafac/obs-cli/releases
132 | ADD https://github.com/leafac/obs-cli/releases/download/v2.2.3/obs-cli-linux /usr/local/bin/obs-cli-js
133 | RUN chmod 755 /usr/local/bin/obs-cli-js
134 |
135 | # [Optional] Set the default user. Omit if you want to keep the default as root.
136 | USER $USERNAME
137 |
138 |
--------------------------------------------------------------------------------
/vignettes/tidying_calendar.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "tidying_calendar"
3 | output: rmarkdown::html_vignette
4 | vignette: >
5 | %\VignetteIndexEntry{tidying_calendar}
6 | %\VignetteEngine{knitr::rmarkdown}
7 | %\VignetteEncoding{UTF-8}
8 | ---
9 |
10 | ```{r, include = FALSE}
11 | library(toastui)
12 | library(calendar)
13 | library(caldav)
14 | library(golem)
15 | library(dplyr)
16 | library(tidyr)
17 | library(clock)
18 |
19 | knitr::opts_chunk$set(
20 | collapse = TRUE,
21 | comment = "#>"
22 | )
23 | ```
24 |
25 | # Tidying up calendar metadata
26 |
27 | In this vignette, we will explore how to tidy up the raw import of a calendar from the backend server so it meets the requirements for the `{toastui}` calendar html widget.
28 |
29 | ## Required Format
30 |
31 | First we will load the built-in calendar data frame provided by `{toastui}`
32 |
33 | ```{r toastcal}
34 | toast_df <- toastui::cal_demo_data("month")
35 | dplyr::glimpse(toast_df)
36 | ```
37 |
38 | There isn't substantial documentation on the requirements, but my best guess is the following columns are required:
39 |
40 | * `calendarId`: numeric id
41 | * `title`: Event title
42 | * `body`: Event description
43 | * `recurrenceRole`: If the event is a single occurrance, this is set to `NA`, otherwise for a weekly event it should be `Every week`. I'm not sure what the other valid values would be
44 | * `start`: Event start time. Format is `YYYY-MM-DD XX:YY:ZZ`
45 | * `end`: Event end time. Format is `YYYY-MM-DD XX:YY:ZZ`
46 | * `category`: Either `allday` for an all-day event, or `time` for a scheduled event not lasting the entire day
47 | * `location`: Location of event. If not needed, it can be `NA`
48 | * `bgColor`: Hex color for the event boc
49 | * `color`: Hex color of font?
50 | * `borderColor`: Hex color of border for the event box
51 |
52 | ## Raw import
53 |
54 | Next we will import the raw ical calendar from the server using custom functions that wrap the `{calendar}` and `{caldav}` packages
55 |
56 | ```{r rawimport}
57 | source("R/mod_cal_viewer_fct_helpers.R")
58 | source("R/app_config.R")
59 | cal_slug <- "data-science-streams"
60 | cal_base_url <- "https://nextcloud.r-podcastdev.link/remote.php/dav/calendars/shinycal"
61 | raw_df <- import_cal(cal_slug = cal_slug, cal_base_url = cal_base_url) %>%
62 | mutate(uid = UID)
63 | ```
64 |
65 | ```{r rawexplore}
66 | dplyr::glimpse(raw_df)
67 | ```
68 |
69 | We see that there is a fair bit of cleanup to do. A summary of issues that need to be solved:
70 |
71 | * There are multiple pairs of event start and end times, one pair for each time zone used when making events in the calendar. We need a way to keep only the pair with non-missing data and record the time zone used
72 | * The timestamp format is much different. An example is `20210514T210000`
73 | * `RRULE` looks similar in spirit to `recurrenceRole` but the values are different. Example is `FREQ=WEEKLY;BYDAY=WE;UNTIL=20210824T220000Z` and `FREQ=WEEKLY;BYDAY=TH`
74 | * Unclear what `SEQUENCE` does. The values are integers in character format.
75 |
76 | ## Let the tidying begin
77 |
78 | We will first tackle the multiple start and end times issue. I think the following approach should work:
79 |
80 | 1. Grab all columns that begin with `DTSTART` or `DTEND`
81 | 1. Create a unique ID column for row index to server as a unique identifier
82 | 1. Transpose to a long format
83 | 1. Strip out the time zone information from the time values and make them a separate column. It looks like we should be able to split by `;`
84 | 1. Make the data wide again with column names `id`, `start`, `end`, and `timezone`
85 |
86 | ```{r dttyding}
87 | dt_df <- raw_df %>%
88 | select(uid, starts_with("DTSTART"), starts_with("DTEND")) %>%
89 | tidyr::pivot_longer(!uid, names_to = c("dt_type", "timezone"), names_sep = ";", values_to = "time") %>%
90 | filter(!is.na(time)) %>%
91 | tidyr::pivot_wider(names_from = c(dt_type), values_from = time)
92 | #mutate(timezone = stringr::str_remove_all(timezone, "TZID="))
93 |
94 | ```
95 |
96 | That worked fairly well! Now we need to conform the start and end column values to match the string format used in `{toastui}`. Recall the raw and desired formats:
97 |
98 | **Raw**: `"20210514T210000"`
99 | **Final**: `"2021-05-14 21:00:00"`
100 |
101 | We will use the `{clock}` package to parse the raw formats (while incorporating time zones) and then get the desired formatting
102 |
103 | ```{r clock_exp}
104 | # experiment with clock package
105 | x <- "20210514T210000"
106 | x_zone <- "Europe/Zurich"
107 |
108 | y <- date_time_parse(x, zone = x_zone, format = "%Y%m%dT%H%M%S") %>%
109 | as_naive_time() %>%
110 | as_zoned_time(., x_zone)
111 | y
112 |
113 | # the character version
114 | #as.character(y)
115 | ```
116 |
117 | Looks great! Now let's apply that to the processed data frame
118 |
119 | ```{r apply_date_format}
120 | time_parser <- function(x, zone, format = "%Y%m%dT%H%M%S", convert_to_char = FALSE) {
121 | x <- clock::date_time_parse(x, zone, format = format) %>%
122 | as_naive_time() %>%
123 | as_zoned_time(., zone)
124 |
125 | if (convert_to_char) {
126 | x <- as.character(x)
127 | }
128 | return(x)
129 | }
130 |
131 | dt_df2 <- dt_df %>%
132 | mutate(
133 | timezone = stringr::str_remove_all(timezone, "TZID="),
134 | start_clock = purrr::map2(DTSTART, timezone, ~time_parser(.x, .y)),
135 | #start = as.character(start_clock),
136 | end_clock = purrr::map2(DTEND, timezone, ~time_parser(.x, .y)),
137 | #end = as.character(end_clock)
138 | ) %>%
139 | select(uid, start_clock, end_clock, timezone) %>%
140 | tidyr::unnest(cols = c(start_clock, end_clock))
141 |
142 | dplyr::glimpse(dt_df2)
143 | ```
144 |
145 | Now we tackle the recurrence rule coding
146 |
147 | ```{r recurrance}
148 | rec_df <- raw_df %>%
149 | mutate(recurrenceRule = case_when(
150 | stringr::str_detect(RRULE, "FREQ=WEEKLY") ~ "Every week",
151 | TRUE ~ RRULE
152 | )) %>%
153 | select(uid, recurrenceRule)
154 |
155 | ```
156 |
157 | I think we have what we need, so let's put this all together
158 |
159 | ```{r final_polish}
160 | final_df <- raw_df %>%
161 | select(uid, title = SUMMARY, location = LOCATION) %>%
162 | left_join(dt_df2, by = "uid") %>%
163 | left_join(rec_df, by = "uid")
164 | ```
165 |
166 | Now let's see what happens when we view the times according to a particular time zone
167 |
168 | ```{r timeplay}
169 | final_df2 <- final_df %>%
170 | mutate(start_clock = zoned_time_set_zone(start_clock, "Europe/Zurich"),
171 | end_clock = zoned_time_set_zone(end_clock, "Europe/Zurich"))
172 | ```
173 |
174 | Let's give the backend function a try
175 |
176 | ```{r backendfnct}
177 | source("R/mod_cal_viewer_fct_helpers.R")
178 | df8 <- process_cal(raw_df)
179 | ```
--------------------------------------------------------------------------------
/.devcontainer/library-scripts/common-debian.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | #-------------------------------------------------------------------------------------------------------------
3 | # Copyright (c) Microsoft Corporation. All rights reserved.
4 | # Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
5 | #-------------------------------------------------------------------------------------------------------------
6 |
7 | # Syntax: ./common-debian.sh [install zsh flag] [username] [user UID] [user GID] [upgrade packages flag]
8 |
9 | INSTALL_ZSH=${1:-"true"}
10 | INSTALL_FISH=${2:-"true"}
11 | USERNAME=${3:-"vscode"}
12 | USER_UID=${4:-1000}
13 | USER_GID=${5:-1000}
14 | UPGRADE_PACKAGES=${6:-"true"}
15 |
16 | set -e
17 |
18 | if [ "$(id -u)" -ne 0 ]; then
19 | echo -e 'Script must be run a root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
20 | exit 1
21 | fi
22 |
23 | # Treat a user name of "none" as root
24 | if [ "${USERNAME}" = "none" ] || [ "${USERNAME}" = "root" ]; then
25 | USERNAME=root
26 | USER_UID=0
27 | USER_GID=0
28 | fi
29 |
30 | # Load markers to see which steps have already run
31 | MARKER_FILE="/usr/local/etc/vscode-dev-containers/common"
32 | if [ -f "${MARKER_FILE}" ]; then
33 | echo "Marker file found:"
34 | cat "${MARKER_FILE}"
35 | source "${MARKER_FILE}"
36 | fi
37 |
38 | # Ensure apt is in non-interactive to avoid prompts
39 | export DEBIAN_FRONTEND=noninteractive
40 |
41 | # Function to call apt-get if needed
42 | apt-get-update-if-needed()
43 | {
44 | if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then
45 | echo "Running apt-get update..."
46 | apt-get update
47 | else
48 | echo "Skipping apt-get update."
49 | fi
50 | }
51 |
52 | # Run install apt-utils to avoid debconf warning then verify presence of other common developer tools and dependencies
53 | if [ "${PACKAGES_ALREADY_INSTALLED}" != "true" ]; then
54 | apt-get-update-if-needed
55 |
56 | PACKAGE_LIST="apt-utils \
57 | git \
58 | openssh-client \
59 | less \
60 | iproute2 \
61 | procps \
62 | curl \
63 | wget \
64 | unzip \
65 | nano \
66 | jq \
67 | lsb-release \
68 | ca-certificates \
69 | apt-transport-https \
70 | dialog \
71 | gnupg2 \
72 | libc6 \
73 | libgcc1 \
74 | libgssapi-krb5-2 \
75 | libicu[0-9][0-9] \
76 | liblttng-ust0 \
77 | libstdc++6 \
78 | zlib1g \
79 | locales \
80 | sudo"
81 |
82 | # Install libssl1.1 if available
83 | if [[ ! -z $(apt-cache --names-only search ^libssl1.1$) ]]; then
84 | PACKAGE_LIST="${PACKAGE_LIST} libssl1.1"
85 | fi
86 |
87 | # Install appropriate version of libssl1.0.x if available
88 | LIBSSL=$(dpkg-query -f '${db:Status-Abbrev}\t${binary:Package}\n' -W 'libssl1\.0\.?' 2>&1 || echo '')
89 | if [ "$(echo "$LIBSSL" | grep -o 'libssl1\.0\.[0-9]:' | uniq | sort | wc -l)" -eq 0 ]; then
90 | if [[ ! -z $(apt-cache --names-only search ^libssl1.0.2$) ]]; then
91 | # Debian 9
92 | PACKAGE_LIST="${PACKAGE_LIST} libssl1.0.2"
93 | elif [[ ! -z $(apt-cache --names-only search ^libssl1.0.0$) ]]; then
94 | # Ubuntu 18.04, 16.04, earlier
95 | PACKAGE_LIST="${PACKAGE_LIST} libssl1.0.0"
96 | fi
97 | fi
98 |
99 | echo "Packages to verify are installed: ${PACKAGE_LIST}"
100 | apt-get -y install --no-install-recommends ${PACKAGE_LIST} 2> >( grep -v 'debconf: delaying package configuration, since apt-utils is not installed' >&2 )
101 |
102 | PACKAGES_ALREADY_INSTALLED="true"
103 | fi
104 |
105 | # Get to latest versions of all packages
106 | if [ "${UPGRADE_PACKAGES}" = "true" ]; then
107 | apt-get-update-if-needed
108 | apt-get -y upgrade --no-install-recommends
109 | apt-get autoremove -y
110 | fi
111 |
112 | # Ensure at least the en_US.UTF-8 UTF-8 locale is available.
113 | # Common need for both applications and things like the agnoster ZSH theme.
114 | if [ "${LOCALE_ALREADY_SET}" != "true" ]; then
115 | echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen
116 | locale-gen
117 | LOCALE_ALREADY_SET="true"
118 | fi
119 |
120 | # Create or update a non-root user to match UID/GID - see https://aka.ms/vscode-remote/containers/non-root-user.
121 | if id -u $USERNAME > /dev/null 2>&1; then
122 | # User exists, update if needed
123 | if [ "$USER_GID" != "$(id -G $USERNAME)" ]; then
124 | groupmod --gid $USER_GID $USERNAME
125 | usermod --gid $USER_GID $USERNAME
126 | fi
127 | if [ "$USER_UID" != "$(id -u $USERNAME)" ]; then
128 | usermod --uid $USER_UID $USERNAME
129 | fi
130 | else
131 | # Create user
132 | groupadd --gid $USER_GID $USERNAME
133 | useradd -s /bin/bash --uid $USER_UID --gid $USER_GID -m $USERNAME
134 | fi
135 |
136 | # Add add sudo support for non-root user
137 | if [ "${EXISTING_NON_ROOT_USER}" != "${USERNAME}" ]; then
138 | echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME
139 | chmod 0440 /etc/sudoers.d/$USERNAME
140 | EXISTING_NON_ROOT_USER="${USERNAME}"
141 | fi
142 |
143 | # Ensure ~/.local/bin is in the PATH for root and non-root users for bash. (zsh is later)
144 | if [ "${DOT_LOCAL_ALREADY_ADDED}" != "true" ]; then
145 | echo "export PATH=\$PATH:\$HOME/.local/bin" | tee -a /root/.bashrc >> /home/$USERNAME/.bashrc
146 | chown $USER_UID:$USER_GID /home/$USERNAME/.bashrc
147 | DOT_LOCAL_ALREADY_ADDED="true"
148 | fi
149 |
150 | # Optionally install and configure zsh
151 | if [ "${INSTALL_ZSH}" = "true" ] && [ ! -d "/root/.oh-my-zsh" ] && [ "${ZSH_ALREADY_INSTALLED}" != "true" ]; then
152 | apt-get-update-if-needed
153 | apt-get install -y zsh
154 | curl -fsSLo- https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh | bash 2>&1
155 | echo "export PATH=\$PATH:\$HOME/.local/bin" >> /root/.zshrc
156 | if [ "${USERNAME}" != "root" ]; then
157 | cp -fR /root/.oh-my-zsh /home/$USERNAME
158 | cp -f /root/.zshrc /home/$USERNAME
159 | sed -i -e "s/\/root\/.oh-my-zsh/\/home\/$USERNAME\/.oh-my-zsh/g" /home/$USERNAME/.zshrc
160 | chown -R $USER_UID:$USER_GID /home/$USERNAME/.oh-my-zsh /home/$USERNAME/.zshrc
161 | fi
162 | ZSH_ALREADY_INSTALLED="true"
163 | fi
164 |
165 | # Optionally install and configure fish
166 | if [ "${INSTALL_FISH}" = "true" ] && [ "${FISH_ALREADY_INSTALLED}" != "true" ]; then
167 | apt-get-update-if-needed
168 | apt-get install -y fish
169 | #curl -fsSLo- https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh | bash 2>&1
170 | #echo "export PATH=\$PATH:\$HOME/.local/bin" >> /root/.zshrc
171 | #if [ "${USERNAME}" != "root" ]; then
172 | # cp -fR /root/.oh-my-zsh /home/$USERNAME
173 | # cp -f /root/.zshrc /home/$USERNAME
174 | # sed -i -e "s/\/root\/.oh-my-zsh/\/home\/$USERNAME\/.oh-my-zsh/g" /home/$USERNAME/.zshrc
175 | # chown -R $USER_UID:$USER_GID /home/$USERNAME/.oh-my-zsh /home/$USERNAME/.zshrc
176 | #fi
177 | FISH_ALREADY_INSTALLED="true"
178 | fi
179 |
180 | # Write marker file
181 | mkdir -p "$(dirname "${MARKER_FILE}")"
182 | echo -e "\
183 | PACKAGES_ALREADY_INSTALLED=${PACKAGES_ALREADY_INSTALLED}\n\
184 | LOCALE_ALREADY_SET=${LOCALE_ALREADY_SET}\n\
185 | EXISTING_NON_ROOT_USER=${EXISTING_NON_ROOT_USER}\n\
186 | DOT_LOCAL_ALREADY_ADDED=${DOT_LOCAL_ALREADY_ADDED}\n\
187 | ZSH_ALREADY_INSTALLED=${ZSH_ALREADY_INSTALLED}\n\
188 | FISH_ALREADY_INSTALLED=${FISH_ALREADY_INSTALLED}" > "${MARKER_FILE}"
189 |
--------------------------------------------------------------------------------
/vignettes/streamer_info.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "streamer_info"
3 | output: rmarkdown::html_vignette
4 | vignette: >
5 | %\VignetteIndexEntry{streamer_info}
6 | %\VignetteEngine{knitr::rmarkdown}
7 | %\VignetteEncoding{UTF-8}
8 | ---
9 |
10 | ```{r, include = FALSE}
11 | knitr::opts_chunk$set(
12 | collapse = TRUE,
13 | comment = "#>"
14 | )
15 | ```
16 |
17 | ## Obtaining metadata associated with streamers
18 |
19 | The goal of this vignette is to illustrate how we can get interesting metadata associated with each streamer via the Twitch and YouTube APIs.
20 |
21 | ## Setup
22 |
23 | ```{r setup, include = FALSE}
24 | library(twitchr)
25 | library(httr)
26 | library(calendar)
27 | library(caldav)
28 | library(dplyr)
29 | library(yaml)
30 | library(clock)
31 |
32 |
33 | readRenviron(".Renviron")
34 | ```
35 |
36 | ## Twitch streamer metadata
37 |
38 | ```{r twitchauth}
39 | # set up authentication via environment variables
40 | twitch_auth()
41 | ```
42 |
43 | Authentication is successful! Now let's experiment with the functions in the `twitchr` package, as well as custom explorations of obtaining a streamer's calendar via the Twitch API directly, see docs [here](https://dev.twitch.tv/docs/api/reference/#get-channel-icalendar)
44 |
45 |
46 | ## Obtain streamer YAML file data
47 |
48 | ```{r yamlparsing}
49 | yml_file <- "data-raw/streamers.yml"
50 | yml_data <- yaml::read_yaml(file = yml_file)
51 |
52 | # functions are in separate script now
53 | source("R/mod_cal_viewer_fct_helpers.R")
54 |
55 | streamer_data <- tibble(yml_data$streamers) %>%
56 | tidyr::unnest_wider(1) %>%
57 | filter(platform == "twitch") %>%
58 | mutate(id = purrr::map_chr(user_id, ~get_twitch_id(user_name = .x))) %>%
59 | mutate(schedule_data = purrr::map(id, ~get_twitch_schedule(.x)),
60 | video_id = purrr::map_chr(id, ~get_twitch_videos(.x)))
61 | ```
62 | ### Import calendar
63 |
64 | ```{r caltry}
65 |
66 | user <- get_users(login = "aftonsteps")
67 |
68 | r <- httr::GET("https://api.twitch.tv/helix/schedule/icalendar", query = list(broadcaster_id = user$id))
69 | status_code(r)
70 | x <- content(r, "text")
71 |
72 | res <- withr::with_tempfile("tf", {
73 | cat(x, file = tf)
74 | ic_read(tf)
75 | })
76 | ```
77 |
78 | ## Import last two weeks of video data in case schedule is not available
79 |
80 | ```{r twitchvideos}
81 | # functions are in separate script now
82 | source("R/mod_cal_viewer_fct_helpers.R")
83 |
84 | user <- get_users(login = "nickwan_datasci")
85 |
86 | #twitch_clips <- twitchr::get_all_clips(broadcaster_id = user$id)
87 |
88 | r <- httr::GET("https://api.twitch.tv/helix/videos", query = list(user_id = user$id, period = "week"))
89 | status_code(r)
90 |
91 |
92 | res <- httr::content(r, "parsed") %>%
93 | purrr::pluck("data") %>%
94 | tibble::tibble() %>%
95 | tidyr::unnest_wider(1)
96 |
97 | current_weekday <- date_now("America/New_York") %>%
98 | as_year_month_weekday() %>%
99 | get_day()
100 |
101 | prev_week_date <- date_now("America/New_York") %>%
102 | clock::date_shift(target = weekday(current_weekday), which = "previous", boundary = "advance")
103 |
104 | current_sunday <- date_now("America/New_York") %>%
105 | clock::date_shift(target = weekday(clock_weekdays$sunday), which = "previous")
106 |
107 | shift_up_week <- function(x, convert = TRUE) {
108 | if (!convert) {
109 | return(x)
110 | } else {
111 | # get current day of week from supplied date
112 | x_weekday <- clock::as_year_month_weekday(x) %>% clock::get_day()
113 |
114 | res <- clock::date_shift(x, target = clock::weekday(x_weekday), which = "next", boundary = "advance")
115 |
116 | return(res)
117 | }
118 | }
119 |
120 | res_final <- res %>%
121 | mutate(start = purrr::map(created_at, ~time_parser(.x, convert_to_char = FALSE))) %>%
122 | mutate(start = purrr::map(start, ~clock::as_date_time(.x, zone = "America/New_York"))) %>%
123 | mutate(duration2 = purrr::map_dbl(duration, ~parse_duration(.x, "seconds"))) %>%
124 | tidyr::unnest(cols = c(start)) %>%
125 | mutate(start = purrr::map(start, ~round_time(.x))) %>%
126 | mutate(end = purrr::map2(start, duration2, ~compute_end_clock(.x, .y))) %>%
127 | mutate(category = "time",
128 | recurrenceRule = "Every week",
129 | start_time = NA,
130 | end_time = NA) %>%
131 | dplyr::select(start_time, start, end_time, end, title, category, recurrenceRule) %>%
132 | tidyr::unnest(cols = c(start, end)) %>%
133 | filter(start > prev_week_date) %>%
134 | mutate(before_week_ind = start < current_sunday) %>%
135 | mutate(start = purrr::map2(start, before_week_ind, ~shift_up_week(.x, .y))) %>%
136 | mutate(end = purrr::map2(end, before_week_ind, ~shift_up_week(.x, .y))) %>%
137 | mutate(start = as.character(start), end = as.character(end))
138 |
139 | # experiment with time shifting
140 | # ideas
141 | # - based on current day, find the first day of the current week
142 | # - if the start date time is on a date before the first day of the week, then shift the date up. Otherwise, leave as it is
143 |
144 | test_weekdate <- clock::as_year_month_weekday(res_final$start[1])
145 | test_date <- clock::as_year_month_day(res_final$start[1])
146 | get_week(test_date)
147 |
148 | sunday <- clock::weekday(clock_weekdays$sunday)
149 |
150 | bench_date <- clock::date_shift(res_final$start[1], sunday, which = "previous")
151 |
152 | clock::date_shift(res_final$start[1], sunday, which = "previous")
153 |
154 | res_final$start[1] < bench_date
155 |
156 | clock::weekday(clock::date_now(zone = "America/New_York"))
157 |
158 | clock::date_shift(test_date, target = sunday, which = "previous")
159 |
160 | # experiment with rounding
161 | test_time <- clock::as_date_time(twitch_vids$start_clock[[1]], "America/New_York")
162 |
163 | # https://stackoverflow.com/questions/27397332/find-round-the-time-to-nearest-half-an-hour-from-a-single-file
164 | test_time2 <- as.POSIXlt(test_time)
165 | test_time2$min
166 |
167 | test_time2$min <- round(test_time2$min / 30) * 30
168 | test_time2
169 |
170 | test_time2$sec
171 | test_time2$sec <- floor(test_time2$sec / 60)
172 | test_time2
173 |
174 | as.POSIXct(test_time2)
175 |
176 |
177 | clock::date_round(test_time, "hour")
178 |
179 |
180 | twitch_vids$end_clock[[1]]
181 | twitch_vids$start_clock[[1]]
182 | clock::add_seconds(twitch_vids$end_clock[[1]], -30)
183 | View(twitch_vids)
184 |
185 |
186 |
187 |
188 | dur_parsed <- stringr::str_match_all(test_duration, dur_regex)
189 | dur_parsed
190 |
191 | r <- httr::GET("https://api.twitch.tv/helix/streams", query = list(user_id = user$id))
192 | status_code(r)
193 |
194 | res2 <- content(r, "parsed")
195 | twitch_streams <- res2
196 |
197 | ```
198 |
199 |
200 | ### Import stream schedule
201 |
202 | ```{r schedule}
203 | r <- httr::GET("https://api.twitch.tv/helix/schedule", query = list(broadcaster_id = user$id))
204 | status_code(r)
205 |
206 | res2 <- content(r, "parsed")
207 |
208 | res3 <- get_schedule(broadcaster_id = user$id) %>%
209 | .[["data"]] %>%
210 | tibble()
211 |
212 | res3
213 | ```
214 |
215 | ### Import additional metadata
216 |
217 | ```{r other-meta}
218 | videos <- get_videos(user_id = user$id, first = 100)
219 |
220 | videos_play <- videos$data %>%
221 | tibble() %>%
222 | mutate(video_id = purrr::map_chr(url, ~{
223 | tmp <- stringr::str_split(.x, "/")
224 | n_items <- length(tmp[[1]])
225 | res <- tmp[[1]][n_items]
226 | return(res)
227 | }))
228 |
229 | videos_df <- videos$data %>%
230 | mutate(video_id = purrr::map_chr(url, ~{
231 | tmp <- stringr::str_split(.x, "/")
232 | n_items <- length(tmp[[1]])
233 | res <- tmp[[1]][n_items]
234 | return(res)
235 | }))
236 |
237 | # video id 1060733999
238 | followers <- get_follows(to_id = user$id, first = 100)
239 | followers_df <- followers$data
240 |
241 | ```
242 |
243 | ## YouTube stream metadata
244 |
245 | ```{r youtube, eval=FALSE}
246 | channel_id <- "UCeiiqmVK07qhY-wvg3IZiZQ"
247 | part <- "liveStreamingDetails"
248 | part <- "snippet,contentDetails"
249 | part <- "snippet,contentDetails,liveStreamingDetails"
250 | #base_url <- "https://www.googleapis.com/youtube/v3/channels"
251 | base_url <- "https://www.googleapis.com/youtube/v3/liveBroadcasts"
252 | base_url <- "https://www.googleapis.com/youtube/v3/videos"
253 | yt_request <- httr::GET(base_url,
254 | query = list(key = Sys.getenv("YOUTUBE_API_KEY"),
255 | id = channel_id,
256 | part = part))
257 |
258 | status_code(yt_request)
259 |
260 | res3 <- content(yt_request, "parsed")
261 | ```
--------------------------------------------------------------------------------
/dev/r_code/wimpys-world-of-streamers-2021-06-08.ics:
--------------------------------------------------------------------------------
1 | BEGIN:VCALENDAR
2 | VERSION:2.0
3 | CALSCALE:GREGORIAN
4 | PRODID:-//SabreDAV//SabreDAV//EN
5 | X-WR-CALNAME:Wimpy's World of Streamers
6 | X-APPLE-CALENDAR-COLOR:#248EB5
7 | REFRESH-INTERVAL;VALUE=DURATION:PT4H
8 | X-PUBLISHED-TTL:PT4H
9 | BEGIN:VTIMEZONE
10 | TZID:Europe/Zurich
11 | BEGIN:DAYLIGHT
12 | TZOFFSETFROM:+0100
13 | TZOFFSETTO:+0200
14 | TZNAME:CEST
15 | DTSTART:19700329T020000
16 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
17 | END:DAYLIGHT
18 | BEGIN:STANDARD
19 | TZOFFSETFROM:+0200
20 | TZOFFSETTO:+0100
21 | TZNAME:CET
22 | DTSTART:19701025T030000
23 | RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
24 | END:STANDARD
25 | END:VTIMEZONE
26 | BEGIN:VTIMEZONE
27 | TZID:Europe/Ljubljana
28 | BEGIN:DAYLIGHT
29 | TZOFFSETFROM:+0100
30 | TZOFFSETTO:+0200
31 | TZNAME:CEST
32 | DTSTART:19700329T020000
33 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
34 | END:DAYLIGHT
35 | BEGIN:STANDARD
36 | TZOFFSETFROM:+0200
37 | TZOFFSETTO:+0100
38 | TZNAME:CET
39 | DTSTART:19701025T030000
40 | RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
41 | END:STANDARD
42 | END:VTIMEZONE
43 | BEGIN:VTIMEZONE
44 | TZID:America/New_York
45 | BEGIN:DAYLIGHT
46 | TZOFFSETFROM:-0500
47 | TZOFFSETTO:-0400
48 | TZNAME:EDT
49 | DTSTART:19700308T020000
50 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
51 | END:DAYLIGHT
52 | BEGIN:STANDARD
53 | TZOFFSETFROM:-0400
54 | TZOFFSETTO:-0500
55 | TZNAME:EST
56 | DTSTART:19701101T020000
57 | RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
58 | END:STANDARD
59 | END:VTIMEZONE
60 | BEGIN:VTIMEZONE
61 | TZID:America/Indiana/Indianapolis
62 | BEGIN:DAYLIGHT
63 | TZOFFSETFROM:-0500
64 | TZOFFSETTO:-0400
65 | TZNAME:EDT
66 | DTSTART:19700308T020000
67 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
68 | END:DAYLIGHT
69 | BEGIN:STANDARD
70 | TZOFFSETFROM:-0400
71 | TZOFFSETTO:-0500
72 | TZNAME:EST
73 | DTSTART:19701101T020000
74 | RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
75 | END:STANDARD
76 | END:VTIMEZONE
77 | BEGIN:VEVENT
78 | CREATED:20210512T060448Z
79 | DTSTAMP:20210512T060706Z
80 | LAST-MODIFIED:20210512T060706Z
81 | SEQUENCE:2
82 | UID:2f4bbb1a-5f0f-43d3-a3c4-b01a8d889a84
83 | DTSTART;TZID=Europe/Zurich:20210514T210000
84 | DTEND;TZID=Europe/Zurich:20210514T220000
85 | SUMMARY:StreamBerry : a DIY StreamDeck\, part 1
86 | LOCATION:Twitch
87 | DESCRIPTION:Let's do some Python magic\, and make a client program connect
88 | to a server program with zero configuration.
89 | END:VEVENT
90 | BEGIN:VEVENT
91 | CREATED:20210524T203932Z
92 | DTSTAMP:20210524T204054Z
93 | LAST-MODIFIED:20210524T204054Z
94 | SEQUENCE:3
95 | UID:397edb19-e6f6-46cf-9640-5b8932729fad
96 | DTSTART;TZID=Europe/Ljubljana:20210529T223000
97 | DTEND;TZID=Europe/Ljubljana:20210530T003000
98 | SUMMARY:Bigpod's Tries to implement all he learned about python in actual p
99 | rogram
100 | END:VEVENT
101 | BEGIN:VEVENT
102 | CREATED:20210524T204217Z
103 | DTSTAMP:20210524T204305Z
104 | LAST-MODIFIED:20210524T204305Z
105 | SEQUENCE:2
106 | UID:50e7645d-2165-4434-bce0-7b38e64c8bbf
107 | DTSTART;TZID=Europe/Ljubljana:20210602T223000
108 | DTEND;TZID=Europe/Ljubljana:20210603T003000
109 | SUMMARY:Bigpod's stream about repositoryManagerNet
110 | END:VEVENT
111 | BEGIN:VEVENT
112 | CREATED:20210529T161306Z
113 | DTSTAMP:20210529T161432Z
114 | LAST-MODIFIED:20210529T161432Z
115 | SEQUENCE:2
116 | UID:9824ae8c-357d-4a33-8ef8-2023621e972e
117 | DTSTART;TZID=Europe/Ljubljana:20210609T223000
118 | DTEND;TZID=Europe/Ljubljana:20210610T003000
119 | RRULE:FREQ=WEEKLY;BYDAY=WE;UNTIL=20210824T220000Z
120 | SUMMARY:bigpods stream titles TBD
121 | END:VEVENT
122 | BEGIN:VEVENT
123 | CREATED:20210524T195012Z
124 | DTSTAMP:20210524T195236Z
125 | LAST-MODIFIED:20210524T195236Z
126 | SEQUENCE:3
127 | UID:f27c3ef5-8f33-4893-b612-22e6c5a32e21
128 | DTSTART;TZID=Europe/Zurich:20210526T210000
129 | DTEND;TZID=Europe/Zurich:20210526T230000
130 | SUMMARY:Tea\, Earl Grey\, Hot ! - Live recording
131 | LOCATION:YouTube + Twitch
132 | DESCRIPTION:We're going to Twitch for the 1st time !
133 | END:VEVENT
134 | BEGIN:VEVENT
135 | CREATED:20210510T195914Z
136 | DTSTAMP:20210510T200001Z
137 | LAST-MODIFIED:20210510T200001Z
138 | SEQUENCE:3
139 | UID:9a6c1e51-d481-4293-b981-32b4f005b437
140 | DTSTART;TZID=America/New_York:20210510T163000
141 | DTEND;TZID=America/New_York:20210510T173000
142 | SUMMARY:Monica - Learning Factory Pt. 2 (Twitch)
143 | END:VEVENT
144 | BEGIN:VEVENT
145 | CREATED:20210520T195427Z
146 | DTSTAMP:20210520T195506Z
147 | LAST-MODIFIED:20210520T195506Z
148 | SEQUENCE:2
149 | UID:6d7008da-5969-4f55-b0e3-c7e1861401c0
150 | DTSTART;TZID=Europe/Ljubljana:20210522T223000
151 | DTEND;TZID=Europe/Ljubljana:20210523T003000
152 | SUMMARY:bigpod trying to learn Python on stream
153 | END:VEVENT
154 | BEGIN:VEVENT
155 | CREATED:20210524T195434Z
156 | DTSTAMP:20210524T195517Z
157 | LAST-MODIFIED:20210524T195517Z
158 | SEQUENCE:4
159 | UID:0172ff7b-44de-4836-b14b-7cebcc6f1835
160 | DTSTART;TZID=Europe/Zurich:20210527T220000
161 | DTEND;TZID=Europe/Zurich:20210527T230000
162 | SUMMARY:Software Boutique 2.0\, part 6
163 | LOCATION:YouTube
164 | END:VEVENT
165 | BEGIN:VEVENT
166 | CREATED:20210528T131756Z
167 | DTSTAMP:20210528T132939Z
168 | LAST-MODIFIED:20210528T132939Z
169 | SEQUENCE:6
170 | UID:7726ab4c-0f9c-4fde-839a-076830ea1435
171 | DTSTART;TZID=Europe/Zurich:20210603T220000
172 | DTEND;TZID=Europe/Zurich:20210603T230000
173 | SUMMARY:Software Boutique 2.0
174 | RRULE:FREQ=WEEKLY;BYDAY=TH
175 | LOCATION:YouTube
176 | EXDATE;TZID=Europe/Zurich:20210603T220000
177 | END:VEVENT
178 | BEGIN:VEVENT
179 | CREATED:20210524T132807Z
180 | DTSTAMP:20210524T132907Z
181 | LAST-MODIFIED:20210524T132907Z
182 | SEQUENCE:2
183 | UID:352ea3ae-982c-4b92-9271-3e5fdbfc85f6
184 | DTSTART;TZID=America/Indiana/Indianapolis:20210525T153000
185 | DTEND;TZID=America/Indiana/Indianapolis:20210525T163000
186 | SUMMARY:Using Docker Slim with R & Shiny containers (Eric and Martin)
187 | END:VEVENT
188 | BEGIN:VEVENT
189 | CREATED:20210424T210448Z
190 | DTSTAMP:20210428T200727Z
191 | LAST-MODIFIED:20210428T200727Z
192 | SEQUENCE:5
193 | UID:b146c8b0-6c21-4d0c-b00a-f018531e7c16
194 | DTSTART;TZID=Europe/Ljubljana:20210428T220000
195 | DTEND;TZID=Europe/Ljubljana:20210429T003000
196 | SUMMARY:Bigpod's stream about RepositoryManagerNet
197 | LOCATION:https://www.youtube.com/watch?v=aeDbsznAvKQ
198 | DESCRIPTION:https://www.youtube.com/watch?v=aeDbsznAvKQ
199 | END:VEVENT
200 | BEGIN:VEVENT
201 | CREATED:20210524T194904Z
202 | DTSTAMP:20210524T195254Z
203 | LAST-MODIFIED:20210524T195254Z
204 | SEQUENCE:3
205 | UID:4206ab89-925a-4a99-8868-56ff280ef96e
206 | DTSTART;TZID=Europe/Zurich:20210525T223000
207 | DTEND;TZID=Europe/Zurich:20210525T233000
208 | SUMMARY:How to use Glimpse to automate the creation of stream titles
209 | LOCATION:Twitch
210 | END:VEVENT
211 | BEGIN:VEVENT
212 | CREATED:20210423T201110Z
213 | DTSTAMP:20210423T201150Z
214 | LAST-MODIFIED:20210423T201150Z
215 | SEQUENCE:2
216 | UID:0a381960-39d1-4693-9e73-0624a53b8b1b
217 | DTSTART;TZID=Europe/Zurich:20210506T213000
218 | DTEND;TZID=Europe/Zurich:20210506T223000
219 | SUMMARY:Software Boutique 2.0\, part 3
220 | END:VEVENT
221 | BEGIN:VEVENT
222 | CREATED:20210423T224127Z
223 | DTSTAMP:20210423T224431Z
224 | LAST-MODIFIED:20210423T224431Z
225 | SEQUENCE:3
226 | UID:c0e5837f-28b6-4a24-ad94-bb684d0d857a
227 | DTSTART;TZID=America/New_York:20210426T163000
228 | DTEND;TZID=America/New_York:20210426T180000
229 | SUMMARY:Monica's Oh My Git Part 2 livestream
230 | LOCATION:https://www.twitch.tv/communiteatime
231 | DESCRIPTION:More shenanigans with learning Git through games!
232 | END:VEVENT
233 | BEGIN:VEVENT
234 | CREATED:20210524T195527Z
235 | DTSTAMP:20210524T195620Z
236 | LAST-MODIFIED:20210524T195620Z
237 | SEQUENCE:2
238 | UID:a72d3df6-be41-4c26-8eb6-12e389f9b6ad
239 | DTSTART;TZID=Europe/Zurich:20210529T210000
240 | DTEND;TZID=Europe/Zurich:20210529T220000
241 | SUMMARY:StreamBerry\, part 2
242 | END:VEVENT
243 | BEGIN:VEVENT
244 | CREATED:20210423T191740Z
245 | DTSTAMP:20210423T192027Z
246 | LAST-MODIFIED:20210423T192027Z
247 | SEQUENCE:3
248 | UID:f09058a5-8d12-454a-86e0-223c2081a798
249 | DTSTART;TZID=America/Indiana/Indianapolis:20210429T153000
250 | DTEND;TZID=America/Indiana/Indianapolis:20210429T163000
251 | SUMMARY:Eric's Intro to R livestream!
252 | DESCRIPTION:Streaming to Twitch: https://www.twitch.tv/rpodcast
253 | END:VEVENT
254 | BEGIN:VEVENT
255 | CREATED:20210510T075724Z
256 | DTSTAMP:20210510T195951Z
257 | LAST-MODIFIED:20210510T195951Z
258 | SEQUENCE:4
259 | UID:3b88e1b7-5860-4598-939e-b30bce3c9374
260 | DTSTART;TZID=Europe/Zurich:20210513T220000
261 | DTEND;TZID=Europe/Zurich:20210513T230000
262 | SUMMARY:Software Boutique 2.0\, part 4
263 | LOCATION:YouTube
264 | DESCRIPTION:Let's bring the curated apps in !
265 | END:VEVENT
266 | BEGIN:VEVENT
267 | DTSTAMP:20210603T183630Z
268 | UID:e6bac299-4e26-47ba-84c9-18de61c96ff0
269 | SUMMARY:Eric's shiny dev series livestream on twitch
270 | DTSTART;TZID=America/New_York:20210608T153000
271 | DTEND;TZID=America/New_York:20210608T163000
272 | SEQUENCE:1
273 | LAST-MODIFIED:20210603T183630Z
274 | END:VEVENT
275 | END:VCALENDAR
276 |
--------------------------------------------------------------------------------
/R/golem_utils_ui.R:
--------------------------------------------------------------------------------
1 | #' Turn an R list into an HTML list
2 | #'
3 | #' @param list An R list
4 | #' @param class a class for the list
5 | #'
6 | #' @return an HTML list
7 | #' @noRd
8 | #'
9 | #' @examples
10 | #' list_to_li(c("a","b"))
11 | #'
12 | #' @importFrom shiny tags tagAppendAttributes tagList
13 | list_to_li <- function(list, class = NULL){
14 | if (is.null(class)){
15 | tagList(
16 | lapply(
17 | list,
18 | tags$li
19 | )
20 | )
21 | } else {
22 | res <- lapply(
23 | list,
24 | tags$li
25 | )
26 | res <- lapply(
27 | res,
28 | function(x) {
29 | tagAppendAttributes(
30 | x,
31 | class = class
32 | )
33 | }
34 | )
35 | tagList(res)
36 | }
37 |
38 | }
39 | #' Turn an R list into corresponding HTML paragraph tags
40 | #'
41 | #' @param list an R list
42 | #' @param class a class for the paragraph tags
43 | #'
44 | #' @return An HTML tag
45 | #' @noRd
46 | #'
47 | #' @examples
48 | #' list_to_p(c("This is the first paragraph", "this is the second paragraph"))
49 | #'
50 | #' @importFrom shiny tags tagAppendAttributes tagList
51 | #'
52 | list_to_p <- function(list, class = NULL){
53 | if (is.null(class)){
54 | tagList(
55 | lapply(
56 | list,
57 | tags$p
58 | )
59 | )
60 | } else {
61 | res <- lapply(
62 | list,
63 | tags$p
64 | )
65 | res <- lapply(
66 | res,
67 | function(x) {
68 | tagAppendAttributes(
69 | x,
70 | class = class
71 | )
72 | }
73 | )
74 | tagList(res)
75 | }
76 |
77 | }
78 |
79 | #' @importFrom shiny tags tagAppendAttributes tagList
80 | named_to_li <- function(list, class = NULL){
81 | if(is.null(class)){
82 | res <- mapply(
83 | function(x, y){
84 | tags$li(
85 | HTML(
86 | sprintf("%s: %s", y, x)
87 | )
88 | )
89 | },
90 | list,
91 | names(list),
92 | SIMPLIFY = FALSE
93 | )
94 | tagList(res)
95 | } else {
96 | res <- mapply(
97 | function(x, y){
98 | tags$li(
99 | HTML(
100 | sprintf("%s: %s", y, x)
101 | )
102 | )
103 | },
104 | list,
105 | names(list),
106 | SIMPLIFY = FALSE
107 | )
108 | res <- lapply(
109 | res,
110 | function(x) {
111 | tagAppendAttributes(
112 | x,
113 | class = class
114 | )
115 | }
116 | )
117 | tagList(res)
118 | }
119 | }
120 |
121 | #' Remove a tag attribute
122 | #'
123 | #' @param tag the tag
124 | #' @param ... the attributes to remove
125 | #'
126 | #' @return a new tag
127 | #' @noRd
128 | #'
129 | #' @examples
130 | #' a <- shiny::tags$p(src = "plop", "pouet")
131 | #' tagRemoveAttributes(a, "src")
132 | tagRemoveAttributes <- function(tag, ...) {
133 | attrs <- as.character(list(...))
134 | for (i in seq_along(attrs)) {
135 | tag$attribs[[ attrs[i] ]] <- NULL
136 | }
137 | tag
138 | }
139 |
140 | #' Hide or display a tag
141 | #'
142 | #' @param tag the tag
143 | #'
144 | #' @return a tag
145 | #' @noRd
146 | #'
147 | #' @examples
148 | #' ## Hide
149 | #' a <- shiny::tags$p(src = "plop", "pouet")
150 | #' undisplay(a)
151 | #' b <- shiny::actionButton("go_filter", "go")
152 | #' undisplay(b)
153 | #'
154 | #' @importFrom shiny tagList
155 | undisplay <- function(tag) {
156 | # if not already hidden
157 | if (
158 | !is.null(tag$attribs$style) &&
159 | !grepl("display:\\s+none", tag$attribs$style)
160 | ) {
161 | tag$attribs$style <- paste(
162 | "display: none;",
163 | tag$attribs$style
164 | )
165 | } else {
166 | tag$attribs$style <- "display: none;"
167 | }
168 | tag
169 | }
170 |
171 | #' @importFrom shiny tagList
172 | display <- function(tag) {
173 | if (
174 | !is.null(tag$attribs$style) &&
175 | grepl("display:\\s+none", tag$attribs$style)
176 | ) {
177 | tag$attribs$style <- gsub(
178 | "(\\s)*display:(\\s)*none(\\s)*(;)*(\\s)*",
179 | "",
180 | tag$attribs$style
181 | )
182 | }
183 | tag
184 | }
185 |
186 | #' Hide an elements by calling jquery hide on it
187 | #'
188 | #' @param id the id of the element to hide
189 | #'
190 | #' @noRd
191 | #'
192 | #' @importFrom shiny tags
193 | jq_hide <- function(id) {
194 | tags$script(sprintf("$('#%s').hide()", id))
195 | }
196 |
197 | #' Add a red star at the end of the text
198 | #'
199 | #' Adds a red star at the end of the text
200 | #' (for example for indicating mandatory fields).
201 | #'
202 | #' @param text the HTLM text to put before the red star
203 | #'
204 | #' @return an html element
205 | #' @noRd
206 | #'
207 | #' @examples
208 | #' with_red_star("Enter your name here")
209 | #'
210 | #' @importFrom shiny tags HTML
211 | with_red_star <- function(text) {
212 | shiny::tags$span(
213 | HTML(
214 | paste0(
215 | text,
216 | shiny::tags$span(
217 | style = "color:red", "*"
218 | )
219 | )
220 | )
221 | )
222 | }
223 |
224 |
225 |
226 | #' Repeat tags$br
227 | #'
228 | #' @param times the number of br to return
229 | #'
230 | #' @return the number of br specified in times
231 | #' @noRd
232 | #'
233 | #' @examples
234 | #' rep_br(5)
235 | #'
236 | #' @importFrom shiny HTML
237 | rep_br <- function(times = 1) {
238 | HTML(rep("
", times = times))
239 | }
240 |
241 | #' Create an url
242 | #'
243 | #' @param url the URL
244 | #' @param text the text to display
245 | #'
246 | #' @return an a tag
247 | #' @noRd
248 | #'
249 | #' @examples
250 | #' enurl("https://www.thinkr.fr", "ThinkR")
251 | #'
252 | #' @importFrom shiny tags
253 | enurl <- function(url, text){
254 | tags$a(href = url, text)
255 | }
256 |
257 | #' Columns wrappers
258 | #'
259 | #' These are convenient wrappers around
260 | #' `column(12, ...)`, `column(6, ...)`, `column(4, ...)`...
261 | #'
262 | #' @noRd
263 | #'
264 | #' @importFrom shiny column
265 | col_12 <- function(...){
266 | column(12, ...)
267 | }
268 |
269 | #' @importFrom shiny column
270 | col_10 <- function(...){
271 | column(10, ...)
272 | }
273 |
274 | #' @importFrom shiny column
275 | col_8 <- function(...){
276 | column(8, ...)
277 | }
278 |
279 | #' @importFrom shiny column
280 | col_6 <- function(...){
281 | column(6, ...)
282 | }
283 |
284 |
285 | #' @importFrom shiny column
286 | col_4 <- function(...){
287 | column(4, ...)
288 | }
289 |
290 |
291 | #' @importFrom shiny column
292 | col_3 <- function(...){
293 | column(3, ...)
294 | }
295 |
296 |
297 | #' @importFrom shiny column
298 | col_2 <- function(...){
299 | column(2, ...)
300 | }
301 |
302 |
303 | #' @importFrom shiny column
304 | col_1 <- function(...){
305 | column(1, ...)
306 | }
307 |
308 |
309 |
310 | #' Make the current tag behave like an action button
311 | #'
312 | #' Only works with compatible tags like button or links
313 | #'
314 | #' @param tag Any compatible tag.
315 | #' @param inputId Unique id. This will host the input value to be used
316 | #' on the server side.
317 | #'
318 | #' @return The modified tag with an extra id and the action button class.
319 | #' @noRd
320 | #'
321 | #' @examples
322 | #' if (interactive()) {
323 | #' library(shiny)
324 | #'
325 | #' link <- a(href = "#", "My super link", style = "color: lightblue;")
326 | #'
327 | #' ui <- fluidPage(
328 | #' make_action_button(link, inputId = "mylink")
329 | #' )
330 | #'
331 | #' server <- function(input, output, session) {
332 | #' observeEvent(input$mylink, {
333 | #' showNotification("Pouic!")
334 | #' })
335 | #' }
336 | #'
337 | #' shinyApp(ui, server)
338 | #'
339 | #' }
340 | make_action_button <- function(tag, inputId = NULL) {
341 | # some obvious checks
342 | if (!inherits(tag, "shiny.tag")) stop("Must provide a shiny tag.")
343 | if (!is.null(tag$attribs$class)) {
344 | if (grep("action-button", tag$attribs$class)) {
345 | stop("tag is already an action button")
346 | }
347 | }
348 | if (is.null(inputId) && is.null(tag$attribs$id)) {
349 | stop("tag does not have any id. Please use inputId to be able to
350 | access it on the server side.")
351 | }
352 |
353 | # handle id
354 | if (!is.null(inputId)) {
355 | if (!is.null(tag$attribs$id)) {
356 | warning(
357 | paste(
358 | "tag already has an id. Please use input$",
359 | tag$attribs$id,
360 | "to access it from the server side. inputId will be ignored."
361 | )
362 | )
363 | } else {
364 | tag$attribs$id <- inputId
365 | }
366 | }
367 |
368 | # handle class
369 | if (is.null(tag$attribs$class)) {
370 | tag$attribs$class <- "action-button"
371 | } else {
372 | tag$attribs$class <- paste(tag$attribs$class, "action-button")
373 | }
374 | # return tag
375 | tag
376 | }
377 |
378 |
379 | # UNCOMMENT AND USE
380 | #
381 | # usethis::use_package("markdown")
382 | # usethis::use_package("rmarkdown")
383 | #
384 | # To use this part of the UI
385 | #
386 | #' #' Include Content From a File
387 | #' #'
388 | #' #' Load rendered RMarkdown from a file and turn into HTML.
389 | #' #'
390 | #' #' @rdname includeRMarkdown
391 | #' #' @export
392 | #' #'
393 | #' #' @importFrom rmarkdown render
394 | #' #' @importFrom markdown markdownToHTML
395 | #' #' @importFrom shiny HTML
396 | #' includeRMarkdown <- function(path){
397 | #'
398 | #' md <- tempfile(fileext = '.md')
399 | #'
400 | #' on.exit(unlink(md),add = TRUE)
401 | #'
402 | #' rmarkdown::render(
403 | #' path,
404 | #' output_format = 'md_document',
405 | #' output_dir = tempdir(),
406 | #' output_file = md,quiet = TRUE
407 | #' )
408 | #'
409 | #' html <- markdown::markdownToHTML(md, fragment.only = TRUE)
410 | #'
411 | #' Encoding(html) <- "UTF-8"
412 | #'
413 | #' return(HTML(html))
414 | #' }
415 |
--------------------------------------------------------------------------------
/R/mod_cal_viewer_fct_helpers.R:
--------------------------------------------------------------------------------
1 | #' @importFrom twitchr get_users
2 | #' @importFrom dplyr filter
3 | #' @noRd
4 | get_twitch_id <- function(user_name) {
5 | user <- get_users(login = user_name)
6 | message(glue::glue("user_name: {user_name} - id: {x}", x = user$id))
7 | res <- dplyr::select(user, id, description, profile_image_url)
8 | return(res)
9 | }
10 |
11 | parse_duration <- function(x,
12 | time_unit = c("seconds", "minutes", "hours"),
13 | dur_regex = "([0-9]{1,2}h)?([0-9]{1,2}m)?([0-9]{1,2}(\\.[0-9]{1,3})?s)?") {
14 | # process to reverse engineer the starting time of each video
15 | # clever regex found at https://stackoverflow.com/a/11293491
16 | # we get a matrix back with 2 rows (second row is meaningless)
17 | # columns are the following
18 | # - col 1: the raw duration string
19 | # - col 2: the "hour" part of the duration string (ex "1h")
20 | # - col 3: the "minute" part of the duration string (ex "1m")
21 | # - col 4: the "second" part of the duration string (ex "1s")
22 | # - col 5: meaningless
23 | time_unit <- match.arg(time_unit)
24 | dur_parsed <- stringr::str_match_all(x, dur_regex)[[1]]
25 |
26 | # set up final duration
27 | dur_final <- 0
28 |
29 | # extract relevant parts of duration matrix
30 | dur_vec <- readr::parse_number(dur_parsed[1, c(2,3,4)])
31 |
32 | names(dur_vec) <- c("hours", "minutes", "seconds")
33 |
34 | time_mult <- switch(
35 | time_unit,
36 | hours = c(1, (1/60), (1/3600)),
37 | minutes = c(60, 1, (1/60)),
38 | seconds = c(3600, 60, 1)
39 | )
40 |
41 | dur_vec <- sum(dur_vec * time_mult)
42 |
43 | return(dur_vec)
44 |
45 | }
46 |
47 | # https://stackoverflow.com/questions/27397332/find-round-the-time-to-nearest-half-an-hour-from-a-single-file
48 | round_time <- function(x) {
49 | x <- as.POSIXlt(x)
50 |
51 | x$min <- round(x$min / 30) * 30
52 | x$sec <- floor(x$sec / 60)
53 |
54 | x <- as.POSIXct(x)
55 | return(x)
56 | }
57 |
58 | shift_week <- function(x, convert = TRUE, which = "next") {
59 | if (!convert) {
60 | return(x)
61 | } else {
62 | # get current day of week from supplied date
63 | x_weekday <- clock::as_year_month_weekday(x) %>% clock::get_day()
64 |
65 | res <- clock::date_shift(x, target = clock::weekday(x_weekday), which = which, boundary = "advance")
66 |
67 | return(res)
68 | }
69 | }
70 |
71 | compute_end_clock <- function(start_clock, stream_length, precision = "hour") {
72 | # truncate the stream length to floor of nearest hour
73 | new_length <- clock::duration_round(clock::duration_seconds(stream_length), precision = precision)
74 | end_clock <- clock::add_hours(start_clock, new_length)
75 | return(end_clock)
76 | }
77 |
78 | time_parser <- function(x, orig_zone = "UTC", new_zone = "America/New_York", format = "%Y-%m-%dT%H:%M:%SZ", ambiguous = "earliest", convert_to_char = TRUE) {
79 | # was format = "%Y%m%dT%H%M%S" for ical
80 |
81 | x <- clock::date_time_parse(x, orig_zone, format = format, ambiguous = ambiguous)
82 | x_z <- clock::as_zoned_time(x)
83 |
84 | # change to the desired time zone
85 | x_final <- clock::zoned_time_set_zone(x_z, new_zone) %>% clock::as_naive_time()
86 |
87 | if (convert_to_char) {
88 | x_final <- as.character(x_final)
89 | }
90 | return(x_final)
91 | }
92 |
93 |
94 | get_twitch_schedule <- function(id) {
95 | r <- httr::GET("https://api.twitch.tv/helix/schedule", query = list(broadcaster_id = id))
96 | status <- httr::status_code(r)
97 |
98 | if (status != 200) {
99 | warning(glue::glue("User {id} does not have valid schedule data. Proceeding to infer a schedule based on videos uploaded (status code {status})"))
100 | r <- httr::GET("https://api.twitch.tv/helix/videos", query = list(user_id = id, period = "week"))
101 | status <- httr::status_code(r)
102 |
103 | if (status != 200) {
104 | warning(glue::glue("User {id} does not have any videos! Skipping ..."))
105 | return(NULL)
106 | } else {
107 | current_weekday <- clock::date_now("America/New_York") %>%
108 | clock::as_year_month_weekday() %>%
109 | clock::get_day()
110 |
111 | prev_week_date <- clock::date_now("America/New_York") %>%
112 | clock::date_shift(target = clock::weekday(current_weekday), which = "previous", boundary = "advance")
113 |
114 | current_sunday <- clock::date_now("America/New_York") %>%
115 | clock::date_shift(target = clock::weekday(clock::clock_weekdays$sunday), which = "previous")
116 |
117 | res <- httr::content(r, "parsed") %>%
118 | purrr::pluck("data") %>%
119 | tibble::tibble() %>%
120 | tidyr::unnest_wider(1)
121 |
122 | res_int <- res %>%
123 | mutate(start = purrr::map(created_at, ~time_parser(.x, convert_to_char = FALSE))) %>%
124 | mutate(start = purrr::map(start, ~clock::as_date_time(.x, zone = "America/New_York"))) %>%
125 | mutate(duration2 = purrr::map_dbl(duration, ~parse_duration(.x, "seconds"))) %>%
126 | tidyr::unnest(cols = c(start)) %>%
127 | mutate(start = purrr::map(start, ~round_time(.x))) %>%
128 | mutate(end = purrr::map2(start, duration2, ~compute_end_clock(.x, .y))) %>%
129 | mutate(category = "time",
130 | recurrenceRule = "Every week",
131 | start_time = NA,
132 | end_time = NA) %>%
133 | tidyr::unnest(cols = c(start, end)) %>%
134 | filter(start > prev_week_date)
135 |
136 | if (nrow(res_int) < 1) {
137 | return(NULL)
138 | } else {
139 | res_final <- res_int %>%
140 | mutate(before_week_ind = start < current_sunday) %>%
141 | mutate(start = purrr::map2(start, before_week_ind, ~shift_week(.x, .y))) %>%
142 | mutate(end = purrr::map2(end, before_week_ind, ~shift_week(.x, .y))) %>%
143 | tidyr::unnest(cols = c(start, end)) %>%
144 | mutate(start = as.character(start), end = as.character(end)) %>%
145 | dplyr::select(start_time, start, end_time, end, title, category, recurrenceRule)
146 | }
147 | }
148 | } else {
149 | res <- httr::content(r, "parsed") %>%
150 | purrr::pluck("data", "segments") %>%
151 | tibble::tibble() %>%
152 | tidyr::unnest_wider(1)
153 |
154 | res_int <- res %>%
155 | mutate(start = purrr::map(start_time, ~time_parser(.x, convert_to_char = FALSE)),
156 | end = purrr::map(end_time, ~time_parser(.x, convert_to_char = FALSE)),
157 | category = "time",
158 | recurrenceRule = "Every week") %>%
159 | dplyr::select(start_time, start, end_time, end, title, category, recurrenceRule)
160 | #tidyr::unnest(cols = c(start, end))
161 |
162 | # grab the first records of each unique stream
163 | res_first <- res_int %>%
164 | dplyr::group_by(title) %>%
165 | dplyr::arrange(title, start) %>%
166 | dplyr::slice(1) %>%
167 | dplyr::ungroup() %>%
168 | mutate(start = purrr::map(start, ~clock::as_date_time(.x, zone = "America/New_York")),
169 | end = purrr::map(end, ~clock::as_date_time(.x, zone = "America/New_York"))) %>%
170 | mutate(start = purrr::map(start, ~shift_week(.x, which = "previous")),
171 | end = purrr::map(end, ~shift_week(.x, which = "previous"))) %>%
172 | mutate(start = purrr::map(start, ~clock::as_naive_time(.x)),
173 | end = purrr::map(end, ~clock::as_naive_time(.x)))
174 |
175 | # bind back together
176 | res_final <- dplyr::bind_rows(
177 | tidyr::unnest(res_first, c("start", "end")),
178 | tidyr::unnest(res_int, c("start", "end"))
179 | ) %>%
180 | mutate(start = as.character(start), end = as.character(end))
181 |
182 |
183 | }
184 | return(res_final)
185 | }
186 |
187 |
188 | get_twitch_videos <- function(id) {
189 | message(glue::glue("twitch id {id}"))
190 | videos <- twitchr::get_videos(user_id = id, first = 100)
191 |
192 | if (is.null(videos)) {
193 | # try getting clips instead
194 | videos <- twitchr::get_all_clips(broadcaster_id = id)
195 | if (is.null(videos)) {
196 | warning(glue::glue("There are no videos for user {id}"))
197 | return(NA)
198 | } else {
199 | videos_play <- videos %>%
200 | dplyr::mutate(video_id = purrr::map_chr(url, ~{
201 | tmp <- stringr::str_split(.x, "/")
202 | n_items <- length(tmp[[1]])
203 | res <- tmp[[1]][n_items]
204 | return(res)
205 | })) %>%
206 | dplyr::slice(1) %>%
207 | dplyr::pull(video_id)
208 | return(videos_play)
209 | }
210 | }
211 |
212 | videos_play <- videos$data %>%
213 | #tibble() %>%
214 | dplyr::mutate(video_id = purrr::map_chr(url, ~{
215 | tmp <- stringr::str_split(.x, "/")
216 | n_items <- length(tmp[[1]])
217 | res <- tmp[[1]][n_items]
218 | return(res)
219 | })) %>%
220 | dplyr::slice(1) %>%
221 | dplyr::pull(video_id)
222 |
223 | return(videos_play)
224 | }
225 |
226 |
227 | #' Import calendar directly from server
228 | #'
229 | #' @param cal_slug string for URL slug of calendar.
230 | #'
231 | #' @return data frame with calendar event contents
232 | #' @export
233 | #' @import caldav
234 | #' @importFrom calendar ic_read ical
235 | import_cal <- function(cal_slug = "wimpys-world-of-streamers", cal_base_url = NULL) {
236 | if (is.null(cal_base_url)) {
237 | cal_base_url <- get_golem_config("cal_base_url")
238 | }
239 |
240 | caldav_url = glue::glue("{cal_base_url}/{cal_slug}")
241 | cal_data <-
242 | caldav::caldav_get_all_simple_auth(
243 | url = caldav_url,
244 | user = Sys.getenv("NEXTCLOUD_USER"),
245 | password = Sys.getenv("NEXTCLOUD_PASSWORD")
246 | )
247 |
248 | x <- cal_data$calendar
249 |
250 | res <- withr::with_tempfile("tf", {
251 | cat(x, file = tf)
252 | ic_read(tf)
253 | })
254 |
255 | return(res)
256 | }
257 |
258 | #' @importFrom dplyr mutate select left_join filter case_when
259 | #' @importFrom clock date_time_parse as_naive_time as_zoned_time zoned_time_set_zone
260 | #' @importFrom purrr map map2
261 | #' @noRd
262 | process_cal <- function(raw_df) {
263 | dt_df <- raw_df %>%
264 | mutate(uid = UID) %>%
265 | select(uid, starts_with("DTSTART"), starts_with("DTEND")) %>%
266 | tidyr::pivot_longer(!uid, names_to = c("dt_type", "timezone"), names_sep = ";", values_to = "time") %>%
267 | filter(!is.na(time)) %>%
268 | tidyr::pivot_wider(names_from = c(dt_type), values_from = time)
269 |
270 | dt_df2 <- dt_df %>%
271 | mutate(
272 | timezone = stringr::str_remove_all(timezone, "TZID="),
273 | start_clock = purrr::map2(DTSTART, timezone, ~time_parser(.x, .y)),
274 | #start = as.character(start_clock),
275 | end_clock = purrr::map2(DTEND, timezone, ~time_parser(.x, .y)),
276 | #end = as.character(end_clock)
277 | ) %>%
278 | select(uid, start_clock, end_clock, timezone) %>%
279 | tidyr::unnest(cols = c(start_clock, end_clock))
280 |
281 | rec_df <- raw_df %>%
282 | mutate(uid = UID) %>%
283 | mutate(recurrenceRule = case_when(
284 | stringr::str_detect(RRULE, "FREQ=WEEKLY") ~ "Every week",
285 | TRUE ~ RRULE
286 | )) %>%
287 | select(uid, recurrenceRule)
288 |
289 | final_df <- raw_df %>%
290 | mutate(uid = UID) %>%
291 | select(uid, title = SUMMARY, location = LOCATION) %>%
292 | left_join(dt_df2, by = "uid") %>%
293 | left_join(rec_df, by = "uid") %>%
294 | mutate(raw = uid)
295 |
296 | return(final_df)
297 | }
298 |
299 | process_raw_timezones <- function() {
300 | source_html <- "inst/app/www/timezones_raw.html"
301 |
302 | # ALL credit goes to Tan for rescuing me yet again!
303 | y <- rvest::read_html(source_html) %>%
304 | rvest::html_element("form select") %>%
305 | rvest::html_children()
306 |
307 | timezone_res <- tibble::tibble(
308 | label = y %>% rvest::html_attr("label"),
309 | value = y %>% rvest::html_attr("value")
310 | ) %>%
311 | tidyr::fill(label) %>%
312 | dplyr::filter(!is.na(value)) %>%
313 | dplyr::group_by(label) %>%
314 | dplyr::summarise(value = list(value)) %>%
315 | tibble::deframe()
316 |
317 | return(timezone_res)
318 | }
--------------------------------------------------------------------------------
/R/mod_cal_viewer.R:
--------------------------------------------------------------------------------
1 | #' cal_viewer UI Function
2 | #'
3 | #' @description A shiny Module.
4 | #'
5 | #' @param id,input,output,session Internal parameters for {shiny}.
6 | #'
7 | #' @noRd
8 | #'
9 | #' @importFrom shiny NS tagList
10 | #' @import toastui
11 | #' @import shinylogs
12 | mod_cal_viewer_ui <- function(id){
13 | ns <- NS(id)
14 | tagList(
15 | fluidRow(
16 | col_3(
17 | radioButtons(
18 | ns("cal_view"),
19 | label = "View Type",
20 | choices = c("day", "week", "month"),
21 | selected = "week",
22 | inline = TRUE
23 | )
24 | ),
25 | col_3(
26 | selectInput(
27 | ns("streamer_select"),
28 | label = "Streamer",
29 | choices = c("nothing")
30 | )
31 | ),
32 | col_3(
33 | colourpicker::colourInput(
34 | ns("entry_color"),
35 | label = "Entry Color",
36 | value = "#0ff1a6"
37 | )
38 | ),
39 | col_3(
40 | colourpicker::colourInput(
41 | ns("entry_font_color"),
42 | label = "Entry Font Color",
43 | palette = "limited",
44 | value = "black",
45 | allowedCols = c("white", "black")
46 | )
47 | )
48 | ),
49 | fluidRow(
50 | col_4(
51 | shinyWidgets::pickerInput(
52 | ns("time_zone"),
53 | label = "Select Time Zone",
54 | choices = c("nothing"),
55 | options = shinyWidgets::pickerOptions(liveSearch = TRUE)
56 | )
57 | )
58 | ),
59 | fluidRow(
60 | col_8(
61 | calendarOutput(ns("calui"))
62 | ),
63 | col_4(
64 | h4("Click an entry to view the streamer's latest VOD!"),
65 | uiOutput(ns("vid"))
66 | )
67 | )
68 | )
69 | }
70 |
71 | #' cal_viewer Server Functions
72 | #'
73 | #' @noRd
74 | mod_cal_viewer_server <- function(id){
75 | moduleServer( id, function(input, output, session){
76 | ns <- session$ns
77 |
78 | # load data set from online storage if running app on production
79 | if (getOption("golem.app.prod")) {
80 | cal_df <- readRDS(url("https://sds-streamer-data.us-east-1.linodeobjects.com/streamer_data_current.rds", "rb"))
81 | } else {
82 | cal_df <- readRDS("prototyping/streamer_data_current.rds")
83 | }
84 |
85 | # perform schedule munging that does not depend on UI
86 | cal_sub <- cal_df %>%
87 | tidyr::unnest(schedule_data) %>%
88 | mutate(calendarId = 1) %>%
89 | mutate(id = seq_len(dplyr::n())) %>%
90 | mutate(raw = map2(title, categories, ~list(title = .x, categories = .y)))
91 |
92 | # reactive value for clicked schedule item
93 | schedule_click <- reactiveVal(NULL)
94 |
95 | # reactive for source schedule data
96 | cal_display_df <- reactiveVal(NULL)
97 |
98 | # reactive for time zone selected
99 | cal_prev_timezone <- reactiveVal(NULL)
100 |
101 | observeEvent(input$fancy, {
102 | schedule_click(input$fancy)
103 | })
104 |
105 | output$vid <- renderUI({
106 | #req(cal_processed())
107 | req(schedule_click())
108 |
109 | video_id <- cal_sub %>%
110 | dplyr::filter(id == schedule_click()$id) %>%
111 | tidyr::unnest(cols = video_data) %>%
112 | dplyr::pull(video_data)
113 |
114 | js_snippet <- glue::glue("
115 | ",
126 | divid = ns("testvid"),
127 | .open = "<<<",
128 | .close = ">>>")
129 |
130 | tags$div(
131 | id = ns("testvid"),
132 | #h2("vid was here")
133 | htmltools::HTML(js_snippet)
134 | )
135 | })
136 |
137 | output$last_changed <- renderPrint({
138 |
139 | req(input$calui_schedules)
140 |
141 | input_lst <- reactiveValuesToList(input)
142 | print(input_lst)
143 | # list(
144 | # click = input$calui_click,
145 | # dates = input$calui_dates,
146 | # schedules = input$calui_schedules
147 | # )
148 | #input$`.shinylogs_lastInput`
149 | # Shiny.setInputValue(el.id + "_dates"
150 | # _add
151 | # _schedules
152 | # _click
153 | # _delete
154 | # _update
155 | # _dates
156 | })
157 |
158 | # on initial load, ensure calendar data matches user's time zone
159 | observeEvent(session$userData$zone, {
160 | message("entered zone observe")
161 |
162 |
163 | # update choices for streamer selection input
164 | shiny::updateSelectInput(
165 | session = session,
166 | inputId = "streamer_select",
167 | choices = c("all", cal_df$user_id),
168 | selected = "all"
169 | )
170 |
171 | # set default in picker input to user's time zone
172 | shinyWidgets::updatePickerInput(
173 | session = session,
174 | inputId = "time_zone",
175 | choices = process_raw_timezones(),
176 | selected = session$userData$zone()
177 | )
178 |
179 | # apply client time zone to schedule data
180 | new_zone <- session$userData$zone()
181 |
182 | cal_sub2 <- cal_sub %>%
183 | mutate(start = purrr::map_chr(start, ~time_parser(.x, orig_zone = "America/New_York", new_zone = new_zone, format = "%Y-%m-%dT%H:%M:%S", convert_to_char = TRUE))) %>%
184 | mutate(end = purrr::map_chr(end, ~time_parser(.x, orig_zone = "America/New_York", new_zone = new_zone, format = "%Y-%m-%dT%H:%M:%S", convert_to_char = TRUE))) %>%
185 | select(., -video_data, -start_time, -end_time, -category)
186 |
187 | cal_display_df(cal_sub2)
188 | })
189 |
190 | observeEvent(input$time_zone, {
191 | req(session$userData$zone())
192 | req(cal_display_df())
193 |
194 | # on default render (when no choices have been updated), do nothing
195 | if (input$time_zone == "nothing") return(NULL)
196 |
197 | # update schedule data with new time zone
198 | prev_zone <- session$userData$zone()
199 |
200 | if (!is.null(cal_prev_timezone())) {
201 | prev_zone <- cal_prev_timezone()
202 | }
203 |
204 | # apply selected time zone to schedule data
205 | if (input$time_zone != prev_zone) {
206 |
207 | new_zone <- input$time_zone
208 | cal_prev_timezone(new_zone)
209 |
210 | cal_sub2 <- cal_display_df() %>%
211 | mutate(start = purrr::map_chr(start, ~time_parser(.x, orig_zone = prev_zone, new_zone = new_zone, format = "%Y-%m-%d %H:%M:%S", convert_to_char = TRUE))) %>%
212 | mutate(end = purrr::map_chr(end, ~time_parser(.x, orig_zone = prev_zone, new_zone = new_zone, format = "%Y-%m-%d %H:%M:%S", convert_to_char = TRUE)))
213 |
214 | cal_display_df(cal_sub2)
215 | }
216 | })
217 |
218 | # reactive for calendar object
219 | cal_display_obj <- reactive({
220 | req(cal_display_df())
221 | req(input$entry_color)
222 | req(input$entry_font_color)
223 |
224 | # apply background and font colors to calendar entries
225 | cal_tmp <- cal_display_df() %>%
226 | mutate(bgColor = ifelse(is.na(bgColor), input$entry_color, bgColor)) %>%
227 | mutate(color = ifelse(is.na(color), input$entry_font_color, color))
228 |
229 | # filter for selected streamers if not "all"
230 | if (input$streamer_select != "nothing") {
231 | if (input$streamer_select != "all") {
232 | cal_tmp <- cal_display_df() %>%
233 | filter(user_id == input$streamer_select)
234 | }
235 | }
236 |
237 | cal_zone <- session$userData$zone()
238 | offsetCalculator <- NULL
239 |
240 | #----------------------------------------------------------------------------
241 | # I tried this to get the current time shown on the calendar to match
242 | # the selected time zone, but I am clearly doing it wrong
243 | #----------------------------------------------------------------------------
244 | # if (input$time_zone == "nothing") {
245 | # cal_zone <- session$userData$zone()
246 | # offsetCalculator <- NULL
247 | # } else {
248 | # cal_zone <- input$time_zone
249 | # offsetCalculator <- 'function(timezoneName, timestamp){ return moment.tz.zone(timezoneName).utcOffset(timestamp); }'
250 | # }
251 |
252 | my_id <- ns("fancy")
253 | toastui::calendar(
254 | #toastui::cal_demo_data(),
255 | cal_tmp,
256 | view = input$cal_view,
257 | scheduleView = list('time'),
258 | useNavigation = TRUE,
259 | useDetailPopup = FALSE,
260 | ) %>%
261 | cal_events(
262 | # schedule elements that we could grab
263 | # - location
264 | # - recurrenceRule
265 | # - body
266 | # - title
267 | cal = .,
268 | clickSchedule = JS(glue::glue('function(event) {console.log(event.schedule.id); Shiny.setInputValue("<>", {raw: event.schedule.raw, id: event.schedule.id, x: event.event.clientX, y: event.event.clientY});}', .open = "<<", .close = ">>"))
269 | ) %>%
270 | cal_timezone(
271 | timezoneName = cal_zone,
272 | displayLabel = NULL,
273 | offsetCalculator = offsetCalculator
274 | ) %>%
275 | cal_week_options(
276 | showTimezoneCollapseButton = TRUE,
277 | timezonesCollapsed = FALSE
278 | )
279 | # cal_theme(
280 | # common.border = "2px solid #E5E9F0",
281 | # month.dayname.borderLeft = "2px solid #E5E9F0",
282 | # common.backgroundColor = "#2E3440",
283 | # #month.dayExceptThisMonth.color = input$entry_color,
284 | # #common.backgroundColor = input$entry_color,
285 | # common.holiday.color = "#88C0D0",
286 | # common.saturday.color = "#88C0D0",
287 | # common.dayname.color = "#ECEFF4",
288 | # common.today.color = "#333"
289 | # )
290 | })
291 |
292 | output$calui <- renderCalendar({
293 | req(cal_display_obj())
294 | cal_display_obj()
295 | })
296 |
297 | # update calendar cell colors when toggled
298 | observeEvent(input$entry_color, {
299 | # TODO: Keep this in case I need it later
300 | #message(glue::glue("color is {color}", color = input$entry_color))
301 | })
302 |
303 | observeEvent(input$fancy, {
304 | removeUI(selector = paste0("#", ns("custom_popup")))
305 | id <- as.numeric(input$fancy$id)
306 | # Get the appropriate line clicked
307 | sched <- cal_sub[cal_sub$id == id, ]
308 |
309 | start_time <- lubridate::as_datetime(sched$start) %>% hms::as_hms()
310 | end_time <- lubridate::as_datetime(sched$end) %>% hms::as_hms()
311 |
312 | insertUI(
313 | selector = "body",
314 | #selector = paste0("#", ns("add")),
315 | ui = absolutePanel(
316 | id = ns("custom_popup"),
317 | top = input$fancy$y,
318 | left = input$fancy$x,
319 | draggable = FALSE,
320 | width = "300px",
321 | tags$div(
322 | style = "background: #FFF; padding: 10px; box-shadow: 0px 0.2em 0.4em rgb(0, 0, 0, 0.8); border-radius: 5px;",
323 | actionLink(
324 | inputId = ns("close_calendar_panel"),
325 | label = NULL,
326 | icon = icon("close"),
327 | style = "position: absolute; top: 5px; right: 5px;"
328 | ),
329 | tags$br(),
330 | tags$div(
331 | style = "text-align: left;",
332 | tags$p(
333 | glue::glue("{user_id} is streaming from {start_time} to {end_time}",
334 | user_id = sched$user_id)
335 | ),
336 | tags$p(
337 | sched$description,
338 | ),
339 | tags$p(
340 | "Categories", list_to_li(sched$raw[[1]]$categories)
341 | )
342 | )
343 | )
344 | )
345 | )
346 | })
347 |
348 | observeEvent(input$close_calendar_panel, {
349 | removeUI(selector = paste0("#", ns("custom_popup")))
350 | })
351 | })
352 | }
353 |
354 | ## To be copied in the UI
355 | # mod_cal_viewer_ui("cal_viewer_ui_1")
356 |
357 | ## To be copied in the server
358 | # mod_cal_viewer_server("cal_viewer_ui_1")
359 |
--------------------------------------------------------------------------------
/dev/r_code/tmp.ics:
--------------------------------------------------------------------------------
1 | BEGIN:VCALENDAR
2 | PRODID:-//IDN nextcloud.com//Calendar app 2.2.0//EN
3 | CALSCALE:GREGORIAN
4 | VERSION:2.0
5 | BEGIN:VEVENT
6 | CREATED:20210512T060448Z
7 | DTSTAMP:20210512T060706Z
8 | LAST-MODIFIED:20210512T060706Z
9 | SEQUENCE:2
10 | UID:2f4bbb1a-5f0f-43d3-a3c4-b01a8d889a84
11 | DTSTART;TZID=Europe/Zurich:20210514T210000
12 | DTEND;TZID=Europe/Zurich:20210514T220000
13 | SUMMARY:StreamBerry : a DIY StreamDeck\, part 1
14 | LOCATION:Twitch
15 | DESCRIPTION:Let's do some Python magic\, and make a client program connect to a server program with zero configuration.
16 | END:VEVENT
17 | BEGIN:VTIMEZONE
18 | TZID:Europe/Zurich
19 | BEGIN:DAYLIGHT
20 | TZOFFSETFROM:+0100
21 | TZOFFSETTO:+0200
22 | TZNAME:CEST
23 | DTSTART:19700329T020000
24 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
25 | END:DAYLIGHT
26 | BEGIN:STANDARD
27 | TZOFFSETFROM:+0200
28 | TZOFFSETTO:+0100
29 | TZNAME:CET
30 | DTSTART:19701025T030000
31 | RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
32 | END:STANDARD
33 | END:VTIMEZONE
34 | END:VCALENDAR BEGIN:VCALENDAR
35 | PRODID:-//IDN nextcloud.com//Calendar app 2.2.0//EN
36 | CALSCALE:GREGORIAN
37 | VERSION:2.0
38 | BEGIN:VEVENT
39 | CREATED:20210524T203932Z
40 | DTSTAMP:20210524T204054Z
41 | LAST-MODIFIED:20210524T204054Z
42 | SEQUENCE:3
43 | UID:397edb19-e6f6-46cf-9640-5b8932729fad
44 | DTSTART;TZID=Europe/Ljubljana:20210529T223000
45 | DTEND;TZID=Europe/Ljubljana:20210530T003000
46 | SUMMARY:Bigpod's Tries to implement all he learned about python in actual program
47 | END:VEVENT
48 | BEGIN:VTIMEZONE
49 | TZID:Europe/Ljubljana
50 | BEGIN:DAYLIGHT
51 | TZOFFSETFROM:+0100
52 | TZOFFSETTO:+0200
53 | TZNAME:CEST
54 | DTSTART:19700329T020000
55 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
56 | END:DAYLIGHT
57 | BEGIN:STANDARD
58 | TZOFFSETFROM:+0200
59 | TZOFFSETTO:+0100
60 | TZNAME:CET
61 | DTSTART:19701025T030000
62 | RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
63 | END:STANDARD
64 | END:VTIMEZONE
65 | END:VCALENDAR BEGIN:VCALENDAR
66 | PRODID:-//IDN nextcloud.com//Calendar app 2.2.0//EN
67 | CALSCALE:GREGORIAN
68 | VERSION:2.0
69 | BEGIN:VEVENT
70 | CREATED:20210524T204217Z
71 | DTSTAMP:20210524T204305Z
72 | LAST-MODIFIED:20210524T204305Z
73 | SEQUENCE:2
74 | UID:50e7645d-2165-4434-bce0-7b38e64c8bbf
75 | DTSTART;TZID=Europe/Ljubljana:20210602T223000
76 | DTEND;TZID=Europe/Ljubljana:20210603T003000
77 | SUMMARY:Bigpod's stream about repositoryManagerNet
78 | END:VEVENT
79 | BEGIN:VTIMEZONE
80 | TZID:Europe/Ljubljana
81 | BEGIN:DAYLIGHT
82 | TZOFFSETFROM:+0100
83 | TZOFFSETTO:+0200
84 | TZNAME:CEST
85 | DTSTART:19700329T020000
86 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
87 | END:DAYLIGHT
88 | BEGIN:STANDARD
89 | TZOFFSETFROM:+0200
90 | TZOFFSETTO:+0100
91 | TZNAME:CET
92 | DTSTART:19701025T030000
93 | RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
94 | END:STANDARD
95 | END:VTIMEZONE
96 | END:VCALENDAR BEGIN:VCALENDAR
97 | PRODID:-//IDN nextcloud.com//Calendar app 2.2.0//EN
98 | CALSCALE:GREGORIAN
99 | VERSION:2.0
100 | BEGIN:VEVENT
101 | CREATED:20210529T161306Z
102 | DTSTAMP:20210529T161432Z
103 | LAST-MODIFIED:20210529T161432Z
104 | SEQUENCE:2
105 | UID:9824ae8c-357d-4a33-8ef8-2023621e972e
106 | DTSTART;TZID=Europe/Ljubljana:20210609T223000
107 | DTEND;TZID=Europe/Ljubljana:20210610T003000
108 | RRULE:FREQ=WEEKLY;BYDAY=WE;UNTIL=20210824T220000Z
109 | SUMMARY:bigpods stream titles TBD
110 | END:VEVENT
111 | BEGIN:VTIMEZONE
112 | TZID:Europe/Ljubljana
113 | BEGIN:DAYLIGHT
114 | TZOFFSETFROM:+0100
115 | TZOFFSETTO:+0200
116 | TZNAME:CEST
117 | DTSTART:19700329T020000
118 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
119 | END:DAYLIGHT
120 | BEGIN:STANDARD
121 | TZOFFSETFROM:+0200
122 | TZOFFSETTO:+0100
123 | TZNAME:CET
124 | DTSTART:19701025T030000
125 | RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
126 | END:STANDARD
127 | END:VTIMEZONE
128 | END:VCALENDAR BEGIN:VCALENDAR
129 | PRODID:-//IDN nextcloud.com//Calendar app 2.2.0//EN
130 | CALSCALE:GREGORIAN
131 | VERSION:2.0
132 | BEGIN:VEVENT
133 | CREATED:20210524T195012Z
134 | DTSTAMP:20210524T195236Z
135 | LAST-MODIFIED:20210524T195236Z
136 | SEQUENCE:3
137 | UID:f27c3ef5-8f33-4893-b612-22e6c5a32e21
138 | DTSTART;TZID=Europe/Zurich:20210526T210000
139 | DTEND;TZID=Europe/Zurich:20210526T230000
140 | SUMMARY:Tea\, Earl Grey\, Hot ! - Live recording
141 | LOCATION:YouTube + Twitch
142 | DESCRIPTION:We're going to Twitch for the 1st time !
143 | END:VEVENT
144 | BEGIN:VTIMEZONE
145 | TZID:Europe/Zurich
146 | BEGIN:DAYLIGHT
147 | TZOFFSETFROM:+0100
148 | TZOFFSETTO:+0200
149 | TZNAME:CEST
150 | DTSTART:19700329T020000
151 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
152 | END:DAYLIGHT
153 | BEGIN:STANDARD
154 | TZOFFSETFROM:+0200
155 | TZOFFSETTO:+0100
156 | TZNAME:CET
157 | DTSTART:19701025T030000
158 | RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
159 | END:STANDARD
160 | END:VTIMEZONE
161 | END:VCALENDAR BEGIN:VCALENDAR
162 | PRODID:-//IDN nextcloud.com//Calendar app 2.2.0//EN
163 | CALSCALE:GREGORIAN
164 | VERSION:2.0
165 | BEGIN:VEVENT
166 | CREATED:20210510T195914Z
167 | DTSTAMP:20210510T200001Z
168 | LAST-MODIFIED:20210510T200001Z
169 | SEQUENCE:3
170 | UID:9a6c1e51-d481-4293-b981-32b4f005b437
171 | DTSTART;TZID=America/New_York:20210510T163000
172 | DTEND;TZID=America/New_York:20210510T173000
173 | SUMMARY:Monica - Learning Factory Pt. 2 (Twitch)
174 | END:VEVENT
175 | BEGIN:VTIMEZONE
176 | TZID:America/New_York
177 | BEGIN:DAYLIGHT
178 | TZOFFSETFROM:-0500
179 | TZOFFSETTO:-0400
180 | TZNAME:EDT
181 | DTSTART:19700308T020000
182 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
183 | END:DAYLIGHT
184 | BEGIN:STANDARD
185 | TZOFFSETFROM:-0400
186 | TZOFFSETTO:-0500
187 | TZNAME:EST
188 | DTSTART:19701101T020000
189 | RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
190 | END:STANDARD
191 | END:VTIMEZONE
192 | END:VCALENDAR BEGIN:VCALENDAR
193 | PRODID:-//IDN nextcloud.com//Calendar app 2.2.0//EN
194 | CALSCALE:GREGORIAN
195 | VERSION:2.0
196 | BEGIN:VEVENT
197 | CREATED:20210520T195427Z
198 | DTSTAMP:20210520T195506Z
199 | LAST-MODIFIED:20210520T195506Z
200 | SEQUENCE:2
201 | UID:6d7008da-5969-4f55-b0e3-c7e1861401c0
202 | DTSTART;TZID=Europe/Ljubljana:20210522T223000
203 | DTEND;TZID=Europe/Ljubljana:20210523T003000
204 | SUMMARY:bigpod trying to learn Python on stream
205 | END:VEVENT
206 | BEGIN:VTIMEZONE
207 | TZID:Europe/Ljubljana
208 | BEGIN:DAYLIGHT
209 | TZOFFSETFROM:+0100
210 | TZOFFSETTO:+0200
211 | TZNAME:CEST
212 | DTSTART:19700329T020000
213 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
214 | END:DAYLIGHT
215 | BEGIN:STANDARD
216 | TZOFFSETFROM:+0200
217 | TZOFFSETTO:+0100
218 | TZNAME:CET
219 | DTSTART:19701025T030000
220 | RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
221 | END:STANDARD
222 | END:VTIMEZONE
223 | END:VCALENDAR BEGIN:VCALENDAR
224 | PRODID:-//IDN nextcloud.com//Calendar app 2.2.0//EN
225 | CALSCALE:GREGORIAN
226 | VERSION:2.0
227 | BEGIN:VEVENT
228 | CREATED:20210524T195434Z
229 | DTSTAMP:20210524T195517Z
230 | LAST-MODIFIED:20210524T195517Z
231 | SEQUENCE:4
232 | UID:0172ff7b-44de-4836-b14b-7cebcc6f1835
233 | DTSTART;TZID=Europe/Zurich:20210527T220000
234 | DTEND;TZID=Europe/Zurich:20210527T230000
235 | SUMMARY:Software Boutique 2.0\, part 6
236 | LOCATION:YouTube
237 | END:VEVENT
238 | BEGIN:VTIMEZONE
239 | TZID:Europe/Zurich
240 | BEGIN:DAYLIGHT
241 | TZOFFSETFROM:+0100
242 | TZOFFSETTO:+0200
243 | TZNAME:CEST
244 | DTSTART:19700329T020000
245 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
246 | END:DAYLIGHT
247 | BEGIN:STANDARD
248 | TZOFFSETFROM:+0200
249 | TZOFFSETTO:+0100
250 | TZNAME:CET
251 | DTSTART:19701025T030000
252 | RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
253 | END:STANDARD
254 | END:VTIMEZONE
255 | END:VCALENDAR BEGIN:VCALENDAR
256 | CALSCALE:GREGORIAN
257 | VERSION:2.0
258 | PRODID:-//IDN nextcloud.com//Calendar app 2.2.0//EN
259 | BEGIN:VEVENT
260 | CREATED:20210528T131756Z
261 | DTSTAMP:20210528T132939Z
262 | LAST-MODIFIED:20210528T132939Z
263 | SEQUENCE:6
264 | UID:7726ab4c-0f9c-4fde-839a-076830ea1435
265 | DTSTART;TZID=Europe/Zurich:20210603T220000
266 | DTEND;TZID=Europe/Zurich:20210603T230000
267 | SUMMARY:Software Boutique 2.0
268 | RRULE:FREQ=WEEKLY;BYDAY=TH
269 | LOCATION:YouTube
270 | EXDATE;TZID=Europe/Zurich:20210603T220000
271 | END:VEVENT
272 | BEGIN:VTIMEZONE
273 | TZID:Europe/Zurich
274 | BEGIN:DAYLIGHT
275 | TZOFFSETFROM:+0100
276 | TZOFFSETTO:+0200
277 | TZNAME:CEST
278 | DTSTART:19700329T020000
279 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
280 | END:DAYLIGHT
281 | BEGIN:STANDARD
282 | TZOFFSETFROM:+0200
283 | TZOFFSETTO:+0100
284 | TZNAME:CET
285 | DTSTART:19701025T030000
286 | RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
287 | END:STANDARD
288 | END:VTIMEZONE
289 | END:VCALENDAR BEGIN:VCALENDAR
290 | PRODID:-//IDN nextcloud.com//Calendar app 2.2.0//EN
291 | CALSCALE:GREGORIAN
292 | VERSION:2.0
293 | BEGIN:VEVENT
294 | CREATED:20210524T132807Z
295 | DTSTAMP:20210524T132907Z
296 | LAST-MODIFIED:20210524T132907Z
297 | SEQUENCE:2
298 | UID:352ea3ae-982c-4b92-9271-3e5fdbfc85f6
299 | DTSTART;TZID=America/Indiana/Indianapolis:20210525T153000
300 | DTEND;TZID=America/Indiana/Indianapolis:20210525T163000
301 | SUMMARY:Using Docker Slim with R & Shiny containers (Eric and Martin)
302 | END:VEVENT
303 | BEGIN:VTIMEZONE
304 | TZID:America/Indiana/Indianapolis
305 | BEGIN:DAYLIGHT
306 | TZOFFSETFROM:-0500
307 | TZOFFSETTO:-0400
308 | TZNAME:EDT
309 | DTSTART:19700308T020000
310 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
311 | END:DAYLIGHT
312 | BEGIN:STANDARD
313 | TZOFFSETFROM:-0400
314 | TZOFFSETTO:-0500
315 | TZNAME:EST
316 | DTSTART:19701101T020000
317 | RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
318 | END:STANDARD
319 | END:VTIMEZONE
320 | END:VCALENDAR BEGIN:VCALENDAR
321 | CALSCALE:GREGORIAN
322 | VERSION:2.0
323 | PRODID:-//IDN nextcloud.com//Calendar app 2.2.0//EN
324 | BEGIN:VEVENT
325 | CREATED:20210424T210448Z
326 | DTSTAMP:20210428T200727Z
327 | LAST-MODIFIED:20210428T200727Z
328 | SEQUENCE:5
329 | UID:b146c8b0-6c21-4d0c-b00a-f018531e7c16
330 | DTSTART;TZID=Europe/Ljubljana:20210428T220000
331 | DTEND;TZID=Europe/Ljubljana:20210429T003000
332 | SUMMARY:Bigpod's stream about RepositoryManagerNet
333 | LOCATION:https://www.youtube.com/watch?v=aeDbsznAvKQ
334 | DESCRIPTION:https://www.youtube.com/watch?v=aeDbsznAvKQ
335 | END:VEVENT
336 | BEGIN:VTIMEZONE
337 | TZID:Europe/Ljubljana
338 | BEGIN:DAYLIGHT
339 | TZOFFSETFROM:+0100
340 | TZOFFSETTO:+0200
341 | TZNAME:CEST
342 | DTSTART:19700329T020000
343 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
344 | END:DAYLIGHT
345 | BEGIN:STANDARD
346 | TZOFFSETFROM:+0200
347 | TZOFFSETTO:+0100
348 | TZNAME:CET
349 | DTSTART:19701025T030000
350 | RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
351 | END:STANDARD
352 | END:VTIMEZONE
353 | END:VCALENDAR BEGIN:VCALENDAR
354 | CALSCALE:GREGORIAN
355 | VERSION:2.0
356 | PRODID:-//IDN nextcloud.com//Calendar app 2.2.0//EN
357 | BEGIN:VEVENT
358 | CREATED:20210524T194904Z
359 | DTSTAMP:20210524T195254Z
360 | LAST-MODIFIED:20210524T195254Z
361 | SEQUENCE:3
362 | UID:4206ab89-925a-4a99-8868-56ff280ef96e
363 | DTSTART;TZID=Europe/Zurich:20210525T223000
364 | DTEND;TZID=Europe/Zurich:20210525T233000
365 | SUMMARY:How to use Glimpse to automate the creation of stream titles
366 | LOCATION:Twitch
367 | END:VEVENT
368 | BEGIN:VTIMEZONE
369 | TZID:Europe/Zurich
370 | BEGIN:DAYLIGHT
371 | TZOFFSETFROM:+0100
372 | TZOFFSETTO:+0200
373 | TZNAME:CEST
374 | DTSTART:19700329T020000
375 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
376 | END:DAYLIGHT
377 | BEGIN:STANDARD
378 | TZOFFSETFROM:+0200
379 | TZOFFSETTO:+0100
380 | TZNAME:CET
381 | DTSTART:19701025T030000
382 | RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
383 | END:STANDARD
384 | END:VTIMEZONE
385 | END:VCALENDAR BEGIN:VCALENDAR
386 | PRODID:-//IDN nextcloud.com//Calendar app 2.2.0//EN
387 | CALSCALE:GREGORIAN
388 | VERSION:2.0
389 | BEGIN:VEVENT
390 | CREATED:20210423T201110Z
391 | DTSTAMP:20210423T201150Z
392 | LAST-MODIFIED:20210423T201150Z
393 | SEQUENCE:2
394 | UID:0a381960-39d1-4693-9e73-0624a53b8b1b
395 | DTSTART;TZID=Europe/Zurich:20210506T213000
396 | DTEND;TZID=Europe/Zurich:20210506T223000
397 | SUMMARY:Software Boutique 2.0\, part 3
398 | END:VEVENT
399 | BEGIN:VTIMEZONE
400 | TZID:Europe/Zurich
401 | BEGIN:DAYLIGHT
402 | TZOFFSETFROM:+0100
403 | TZOFFSETTO:+0200
404 | TZNAME:CEST
405 | DTSTART:19700329T020000
406 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
407 | END:DAYLIGHT
408 | BEGIN:STANDARD
409 | TZOFFSETFROM:+0200
410 | TZOFFSETTO:+0100
411 | TZNAME:CET
412 | DTSTART:19701025T030000
413 | RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
414 | END:STANDARD
415 | END:VTIMEZONE
416 | END:VCALENDAR BEGIN:VCALENDAR
417 | PRODID:-//IDN nextcloud.com//Calendar app 2.2.0//EN
418 | CALSCALE:GREGORIAN
419 | VERSION:2.0
420 | BEGIN:VEVENT
421 | CREATED:20210423T224127Z
422 | DTSTAMP:20210423T224431Z
423 | LAST-MODIFIED:20210423T224431Z
424 | SEQUENCE:3
425 | UID:c0e5837f-28b6-4a24-ad94-bb684d0d857a
426 | DTSTART;TZID=America/New_York:20210426T163000
427 | DTEND;TZID=America/New_York:20210426T180000
428 | SUMMARY:Monica's Oh My Git Part 2 livestream
429 | LOCATION:https://www.twitch.tv/communiteatime
430 | DESCRIPTION:More shenanigans with learning Git through games!
431 | END:VEVENT
432 | BEGIN:VTIMEZONE
433 | TZID:America/New_York
434 | BEGIN:DAYLIGHT
435 | TZOFFSETFROM:-0500
436 | TZOFFSETTO:-0400
437 | TZNAME:EDT
438 | DTSTART:19700308T020000
439 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
440 | END:DAYLIGHT
441 | BEGIN:STANDARD
442 | TZOFFSETFROM:-0400
443 | TZOFFSETTO:-0500
444 | TZNAME:EST
445 | DTSTART:19701101T020000
446 | RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
447 | END:STANDARD
448 | END:VTIMEZONE
449 | END:VCALENDAR BEGIN:VCALENDAR
450 | PRODID:-//IDN nextcloud.com//Calendar app 2.2.0//EN
451 | CALSCALE:GREGORIAN
452 | VERSION:2.0
453 | BEGIN:VEVENT
454 | CREATED:20210524T195527Z
455 | DTSTAMP:20210524T195620Z
456 | LAST-MODIFIED:20210524T195620Z
457 | SEQUENCE:2
458 | UID:a72d3df6-be41-4c26-8eb6-12e389f9b6ad
459 | DTSTART;TZID=Europe/Zurich:20210529T210000
460 | DTEND;TZID=Europe/Zurich:20210529T220000
461 | SUMMARY:StreamBerry\, part 2
462 | END:VEVENT
463 | BEGIN:VTIMEZONE
464 | TZID:Europe/Zurich
465 | BEGIN:DAYLIGHT
466 | TZOFFSETFROM:+0100
467 | TZOFFSETTO:+0200
468 | TZNAME:CEST
469 | DTSTART:19700329T020000
470 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
471 | END:DAYLIGHT
472 | BEGIN:STANDARD
473 | TZOFFSETFROM:+0200
474 | TZOFFSETTO:+0100
475 | TZNAME:CET
476 | DTSTART:19701025T030000
477 | RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
478 | END:STANDARD
479 | END:VTIMEZONE
480 | END:VCALENDAR BEGIN:VCALENDAR
481 | PRODID:-//IDN nextcloud.com//Calendar app 2.2.0//EN
482 | CALSCALE:GREGORIAN
483 | VERSION:2.0
484 | BEGIN:VEVENT
485 | CREATED:20210423T191740Z
486 | DTSTAMP:20210423T192027Z
487 | LAST-MODIFIED:20210423T192027Z
488 | SEQUENCE:3
489 | UID:f09058a5-8d12-454a-86e0-223c2081a798
490 | DTSTART;TZID=America/Indiana/Indianapolis:20210429T153000
491 | DTEND;TZID=America/Indiana/Indianapolis:20210429T163000
492 | SUMMARY:Eric's Intro to R livestream!
493 | DESCRIPTION:Streaming to Twitch: https://www.twitch.tv/rpodcast
494 | END:VEVENT
495 | BEGIN:VTIMEZONE
496 | TZID:America/Indiana/Indianapolis
497 | BEGIN:DAYLIGHT
498 | TZOFFSETFROM:-0500
499 | TZOFFSETTO:-0400
500 | TZNAME:EDT
501 | DTSTART:19700308T020000
502 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
503 | END:DAYLIGHT
504 | BEGIN:STANDARD
505 | TZOFFSETFROM:-0400
506 | TZOFFSETTO:-0500
507 | TZNAME:EST
508 | DTSTART:19701101T020000
509 | RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
510 | END:STANDARD
511 | END:VTIMEZONE
512 | END:VCALENDAR BEGIN:VCALENDAR
513 | CALSCALE:GREGORIAN
514 | VERSION:2.0
515 | PRODID:-//IDN nextcloud.com//Calendar app 2.2.0//EN
516 | BEGIN:VEVENT
517 | CREATED:20210510T075724Z
518 | DTSTAMP:20210510T195951Z
519 | LAST-MODIFIED:20210510T195951Z
520 | SEQUENCE:4
521 | UID:3b88e1b7-5860-4598-939e-b30bce3c9374
522 | DTSTART;TZID=Europe/Zurich:20210513T220000
523 | DTEND;TZID=Europe/Zurich:20210513T230000
524 | SUMMARY:Software Boutique 2.0\, part 4
525 | LOCATION:YouTube
526 | DESCRIPTION:Let's bring the curated apps in !
527 | END:VEVENT
528 | BEGIN:VTIMEZONE
529 | TZID:Europe/Zurich
530 | BEGIN:DAYLIGHT
531 | TZOFFSETFROM:+0100
532 | TZOFFSETTO:+0200
533 | TZNAME:CEST
534 | DTSTART:19700329T020000
535 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
536 | END:DAYLIGHT
537 | BEGIN:STANDARD
538 | TZOFFSETFROM:+0200
539 | TZOFFSETTO:+0100
540 | TZNAME:CET
541 | DTSTART:19701025T030000
542 | RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
543 | END:STANDARD
544 | END:VTIMEZONE
545 | END:VCALENDAR BEGIN:VCALENDAR
546 | CALSCALE:GREGORIAN
547 | VERSION:2.0
548 | PRODID:DAVx5/3.3.10-ose ical4j/3.0.24
549 | BEGIN:VEVENT
550 | DTSTAMP:20210603T183630Z
551 | UID:e6bac299-4e26-47ba-84c9-18de61c96ff0
552 | SUMMARY:Eric's shiny dev series livestream on twitch
553 | DTSTART;TZID=America/New_York:20210608T153000
554 | DTEND;TZID=America/New_York:20210608T163000
555 | SEQUENCE:1
556 | LAST-MODIFIED:20210603T183630Z
557 | END:VEVENT
558 | BEGIN:VTIMEZONE
559 | TZID:America/New_York
560 | BEGIN:DAYLIGHT
561 | TZOFFSETFROM:-0500
562 | TZOFFSETTO:-0400
563 | TZNAME:EDT
564 | DTSTART:19700308T020000
565 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
566 | END:DAYLIGHT
567 | BEGIN:STANDARD
568 | TZOFFSETFROM:-0400
569 | TZOFFSETTO:-0500
570 | TZNAME:EST
571 | DTSTART:19701101T020000
572 | RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
573 | END:STANDARD
574 | END:VTIMEZONE
575 | END:VCALENDAR
--------------------------------------------------------------------------------
/renv/activate.R:
--------------------------------------------------------------------------------
1 |
2 | local({
3 |
4 | # the requested version of renv
5 | version <- "0.13.2"
6 |
7 | # the project directory
8 | project <- getwd()
9 |
10 | # avoid recursion
11 | if (!is.na(Sys.getenv("RENV_R_INITIALIZING", unset = NA)))
12 | return(invisible(TRUE))
13 |
14 | # signal that we're loading renv during R startup
15 | Sys.setenv("RENV_R_INITIALIZING" = "true")
16 | on.exit(Sys.unsetenv("RENV_R_INITIALIZING"), add = TRUE)
17 |
18 | # signal that we've consented to use renv
19 | options(renv.consent = TRUE)
20 |
21 | # load the 'utils' package eagerly -- this ensures that renv shims, which
22 | # mask 'utils' packages, will come first on the search path
23 | library(utils, lib.loc = .Library)
24 |
25 | # check to see if renv has already been loaded
26 | if ("renv" %in% loadedNamespaces()) {
27 |
28 | # if renv has already been loaded, and it's the requested version of renv,
29 | # nothing to do
30 | spec <- .getNamespaceInfo(.getNamespace("renv"), "spec")
31 | if (identical(spec[["version"]], version))
32 | return(invisible(TRUE))
33 |
34 | # otherwise, unload and attempt to load the correct version of renv
35 | unloadNamespace("renv")
36 |
37 | }
38 |
39 | # load bootstrap tools
40 | bootstrap <- function(version, library) {
41 |
42 | # attempt to download renv
43 | tarball <- tryCatch(renv_bootstrap_download(version), error = identity)
44 | if (inherits(tarball, "error"))
45 | stop("failed to download renv ", version)
46 |
47 | # now attempt to install
48 | status <- tryCatch(renv_bootstrap_install(version, tarball, library), error = identity)
49 | if (inherits(status, "error"))
50 | stop("failed to install renv ", version)
51 |
52 | }
53 |
54 | renv_bootstrap_tests_running <- function() {
55 | getOption("renv.tests.running", default = FALSE)
56 | }
57 |
58 | renv_bootstrap_repos <- function() {
59 |
60 | # check for repos override
61 | repos <- Sys.getenv("RENV_CONFIG_REPOS_OVERRIDE", unset = NA)
62 | if (!is.na(repos))
63 | return(repos)
64 |
65 | # if we're testing, re-use the test repositories
66 | if (renv_bootstrap_tests_running())
67 | return(getOption("renv.tests.repos"))
68 |
69 | # retrieve current repos
70 | repos <- getOption("repos")
71 |
72 | # ensure @CRAN@ entries are resolved
73 | repos[repos == "@CRAN@"] <- getOption(
74 | "renv.repos.cran",
75 | "https://cloud.r-project.org"
76 | )
77 |
78 | # add in renv.bootstrap.repos if set
79 | default <- c(FALLBACK = "https://cloud.r-project.org")
80 | extra <- getOption("renv.bootstrap.repos", default = default)
81 | repos <- c(repos, extra)
82 |
83 | # remove duplicates that might've snuck in
84 | dupes <- duplicated(repos) | duplicated(names(repos))
85 | repos[!dupes]
86 |
87 | }
88 |
89 | renv_bootstrap_download <- function(version) {
90 |
91 | # if the renv version number has 4 components, assume it must
92 | # be retrieved via github
93 | nv <- numeric_version(version)
94 | components <- unclass(nv)[[1]]
95 |
96 | methods <- if (length(components) == 4L) {
97 | list(
98 | renv_bootstrap_download_github
99 | )
100 | } else {
101 | list(
102 | renv_bootstrap_download_cran_latest,
103 | renv_bootstrap_download_cran_archive
104 | )
105 | }
106 |
107 | for (method in methods) {
108 | path <- tryCatch(method(version), error = identity)
109 | if (is.character(path) && file.exists(path))
110 | return(path)
111 | }
112 |
113 | stop("failed to download renv ", version)
114 |
115 | }
116 |
117 | renv_bootstrap_download_impl <- function(url, destfile) {
118 |
119 | mode <- "wb"
120 |
121 | # https://bugs.r-project.org/bugzilla/show_bug.cgi?id=17715
122 | fixup <-
123 | Sys.info()[["sysname"]] == "Windows" &&
124 | substring(url, 1L, 5L) == "file:"
125 |
126 | if (fixup)
127 | mode <- "w+b"
128 |
129 | utils::download.file(
130 | url = url,
131 | destfile = destfile,
132 | mode = mode,
133 | quiet = TRUE
134 | )
135 |
136 | }
137 |
138 | renv_bootstrap_download_cran_latest <- function(version) {
139 |
140 | spec <- renv_bootstrap_download_cran_latest_find(version)
141 |
142 | message("* Downloading renv ", version, " ... ", appendLF = FALSE)
143 |
144 | type <- spec$type
145 | repos <- spec$repos
146 |
147 | info <- tryCatch(
148 | utils::download.packages(
149 | pkgs = "renv",
150 | destdir = tempdir(),
151 | repos = repos,
152 | type = type,
153 | quiet = TRUE
154 | ),
155 | condition = identity
156 | )
157 |
158 | if (inherits(info, "condition")) {
159 | message("FAILED")
160 | return(FALSE)
161 | }
162 |
163 | # report success and return
164 | message("OK (downloaded ", type, ")")
165 | info[1, 2]
166 |
167 | }
168 |
169 | renv_bootstrap_download_cran_latest_find <- function(version) {
170 |
171 | # check whether binaries are supported on this system
172 | binary <-
173 | getOption("renv.bootstrap.binary", default = TRUE) &&
174 | !identical(.Platform$pkgType, "source") &&
175 | !identical(getOption("pkgType"), "source") &&
176 | Sys.info()[["sysname"]] %in% c("Darwin", "Windows")
177 |
178 | types <- c(if (binary) "binary", "source")
179 |
180 | # iterate over types + repositories
181 | for (type in types) {
182 | for (repos in renv_bootstrap_repos()) {
183 |
184 | # retrieve package database
185 | db <- tryCatch(
186 | as.data.frame(
187 | utils::available.packages(type = type, repos = repos),
188 | stringsAsFactors = FALSE
189 | ),
190 | error = identity
191 | )
192 |
193 | if (inherits(db, "error"))
194 | next
195 |
196 | # check for compatible entry
197 | entry <- db[db$Package %in% "renv" & db$Version %in% version, ]
198 | if (nrow(entry) == 0)
199 | next
200 |
201 | # found it; return spec to caller
202 | spec <- list(entry = entry, type = type, repos = repos)
203 | return(spec)
204 |
205 | }
206 | }
207 |
208 | # if we got here, we failed to find renv
209 | fmt <- "renv %s is not available from your declared package repositories"
210 | stop(sprintf(fmt, version))
211 |
212 | }
213 |
214 | renv_bootstrap_download_cran_archive <- function(version) {
215 |
216 | name <- sprintf("renv_%s.tar.gz", version)
217 | repos <- renv_bootstrap_repos()
218 | urls <- file.path(repos, "src/contrib/Archive/renv", name)
219 | destfile <- file.path(tempdir(), name)
220 |
221 | message("* Downloading renv ", version, " ... ", appendLF = FALSE)
222 |
223 | for (url in urls) {
224 |
225 | status <- tryCatch(
226 | renv_bootstrap_download_impl(url, destfile),
227 | condition = identity
228 | )
229 |
230 | if (identical(status, 0L)) {
231 | message("OK")
232 | return(destfile)
233 | }
234 |
235 | }
236 |
237 | message("FAILED")
238 | return(FALSE)
239 |
240 | }
241 |
242 | renv_bootstrap_download_github <- function(version) {
243 |
244 | enabled <- Sys.getenv("RENV_BOOTSTRAP_FROM_GITHUB", unset = "TRUE")
245 | if (!identical(enabled, "TRUE"))
246 | return(FALSE)
247 |
248 | # prepare download options
249 | pat <- Sys.getenv("GITHUB_PAT")
250 | if (nzchar(Sys.which("curl")) && nzchar(pat)) {
251 | fmt <- "--location --fail --header \"Authorization: token %s\""
252 | extra <- sprintf(fmt, pat)
253 | saved <- options("download.file.method", "download.file.extra")
254 | options(download.file.method = "curl", download.file.extra = extra)
255 | on.exit(do.call(base::options, saved), add = TRUE)
256 | } else if (nzchar(Sys.which("wget")) && nzchar(pat)) {
257 | fmt <- "--header=\"Authorization: token %s\""
258 | extra <- sprintf(fmt, pat)
259 | saved <- options("download.file.method", "download.file.extra")
260 | options(download.file.method = "wget", download.file.extra = extra)
261 | on.exit(do.call(base::options, saved), add = TRUE)
262 | }
263 |
264 | message("* Downloading renv ", version, " from GitHub ... ", appendLF = FALSE)
265 |
266 | url <- file.path("https://api.github.com/repos/rstudio/renv/tarball", version)
267 | name <- sprintf("renv_%s.tar.gz", version)
268 | destfile <- file.path(tempdir(), name)
269 |
270 | status <- tryCatch(
271 | renv_bootstrap_download_impl(url, destfile),
272 | condition = identity
273 | )
274 |
275 | if (!identical(status, 0L)) {
276 | message("FAILED")
277 | return(FALSE)
278 | }
279 |
280 | message("OK")
281 | return(destfile)
282 |
283 | }
284 |
285 | renv_bootstrap_install <- function(version, tarball, library) {
286 |
287 | # attempt to install it into project library
288 | message("* Installing renv ", version, " ... ", appendLF = FALSE)
289 | dir.create(library, showWarnings = FALSE, recursive = TRUE)
290 |
291 | # invoke using system2 so we can capture and report output
292 | bin <- R.home("bin")
293 | exe <- if (Sys.info()[["sysname"]] == "Windows") "R.exe" else "R"
294 | r <- file.path(bin, exe)
295 | args <- c("--vanilla", "CMD", "INSTALL", "-l", shQuote(library), shQuote(tarball))
296 | output <- system2(r, args, stdout = TRUE, stderr = TRUE)
297 | message("Done!")
298 |
299 | # check for successful install
300 | status <- attr(output, "status")
301 | if (is.numeric(status) && !identical(status, 0L)) {
302 | header <- "Error installing renv:"
303 | lines <- paste(rep.int("=", nchar(header)), collapse = "")
304 | text <- c(header, lines, output)
305 | writeLines(text, con = stderr())
306 | }
307 |
308 | status
309 |
310 | }
311 |
312 | renv_bootstrap_platform_prefix <- function() {
313 |
314 | # construct version prefix
315 | version <- paste(R.version$major, R.version$minor, sep = ".")
316 | prefix <- paste("R", numeric_version(version)[1, 1:2], sep = "-")
317 |
318 | # include SVN revision for development versions of R
319 | # (to avoid sharing platform-specific artefacts with released versions of R)
320 | devel <-
321 | identical(R.version[["status"]], "Under development (unstable)") ||
322 | identical(R.version[["nickname"]], "Unsuffered Consequences")
323 |
324 | if (devel)
325 | prefix <- paste(prefix, R.version[["svn rev"]], sep = "-r")
326 |
327 | # build list of path components
328 | components <- c(prefix, R.version$platform)
329 |
330 | # include prefix if provided by user
331 | prefix <- renv_bootstrap_platform_prefix_impl()
332 | if (!is.na(prefix) && nzchar(prefix))
333 | components <- c(prefix, components)
334 |
335 | # build prefix
336 | paste(components, collapse = "/")
337 |
338 | }
339 |
340 | renv_bootstrap_platform_prefix_impl <- function() {
341 |
342 | # if an explicit prefix has been supplied, use it
343 | prefix <- Sys.getenv("RENV_PATHS_PREFIX", unset = NA)
344 | if (!is.na(prefix))
345 | return(prefix)
346 |
347 | # if the user has requested an automatic prefix, generate it
348 | auto <- Sys.getenv("RENV_PATHS_PREFIX_AUTO", unset = NA)
349 | if (auto %in% c("TRUE", "True", "true", "1"))
350 | return(renv_bootstrap_platform_prefix_auto())
351 |
352 | # empty string on failure
353 | ""
354 |
355 | }
356 |
357 | renv_bootstrap_platform_prefix_auto <- function() {
358 |
359 | prefix <- tryCatch(renv_bootstrap_platform_os(), error = identity)
360 | if (inherits(prefix, "error") || prefix %in% "unknown") {
361 |
362 | msg <- paste(
363 | "failed to infer current operating system",
364 | "please file a bug report at https://github.com/rstudio/renv/issues",
365 | sep = "; "
366 | )
367 |
368 | warning(msg)
369 |
370 | }
371 |
372 | prefix
373 |
374 | }
375 |
376 | renv_bootstrap_platform_os <- function() {
377 |
378 | sysinfo <- Sys.info()
379 | sysname <- sysinfo[["sysname"]]
380 |
381 | # handle Windows + macOS up front
382 | if (sysname == "Windows")
383 | return("windows")
384 | else if (sysname == "Darwin")
385 | return("macos")
386 |
387 | # check for os-release files
388 | for (file in c("/etc/os-release", "/usr/lib/os-release"))
389 | if (file.exists(file))
390 | return(renv_bootstrap_platform_os_via_os_release(file, sysinfo))
391 |
392 | # check for redhat-release files
393 | if (file.exists("/etc/redhat-release"))
394 | return(renv_bootstrap_platform_os_via_redhat_release())
395 |
396 | "unknown"
397 |
398 | }
399 |
400 | renv_bootstrap_platform_os_via_os_release <- function(file, sysinfo) {
401 |
402 | # read /etc/os-release
403 | release <- utils::read.table(
404 | file = file,
405 | sep = "=",
406 | quote = c("\"", "'"),
407 | col.names = c("Key", "Value"),
408 | comment.char = "#",
409 | stringsAsFactors = FALSE
410 | )
411 |
412 | vars <- as.list(release$Value)
413 | names(vars) <- release$Key
414 |
415 | # get os name
416 | os <- tolower(sysinfo[["sysname"]])
417 |
418 | # read id
419 | id <- "unknown"
420 | for (field in c("ID", "ID_LIKE")) {
421 | if (field %in% names(vars) && nzchar(vars[[field]])) {
422 | id <- vars[[field]]
423 | break
424 | }
425 | }
426 |
427 | # read version
428 | version <- "unknown"
429 | for (field in c("UBUNTU_CODENAME", "VERSION_CODENAME", "VERSION_ID", "BUILD_ID")) {
430 | if (field %in% names(vars) && nzchar(vars[[field]])) {
431 | version <- vars[[field]]
432 | break
433 | }
434 | }
435 |
436 | # join together
437 | paste(c(os, id, version), collapse = "-")
438 |
439 | }
440 |
441 | renv_bootstrap_platform_os_via_redhat_release <- function() {
442 |
443 | # read /etc/redhat-release
444 | contents <- readLines("/etc/redhat-release", warn = FALSE)
445 |
446 | # infer id
447 | id <- if (grepl("centos", contents, ignore.case = TRUE))
448 | "centos"
449 | else if (grepl("redhat", contents, ignore.case = TRUE))
450 | "redhat"
451 | else
452 | "unknown"
453 |
454 | # try to find a version component (very hacky)
455 | version <- "unknown"
456 |
457 | parts <- strsplit(contents, "[[:space:]]")[[1L]]
458 | for (part in parts) {
459 |
460 | nv <- tryCatch(numeric_version(part), error = identity)
461 | if (inherits(nv, "error"))
462 | next
463 |
464 | version <- nv[1, 1]
465 | break
466 |
467 | }
468 |
469 | paste(c("linux", id, version), collapse = "-")
470 |
471 | }
472 |
473 | renv_bootstrap_library_root_name <- function(project) {
474 |
475 | # use project name as-is if requested
476 | asis <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT_ASIS", unset = "FALSE")
477 | if (asis)
478 | return(basename(project))
479 |
480 | # otherwise, disambiguate based on project's path
481 | id <- substring(renv_bootstrap_hash_text(project), 1L, 8L)
482 | paste(basename(project), id, sep = "-")
483 |
484 | }
485 |
486 | renv_bootstrap_library_root <- function(project) {
487 |
488 | path <- Sys.getenv("RENV_PATHS_LIBRARY", unset = NA)
489 | if (!is.na(path))
490 | return(path)
491 |
492 | path <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT", unset = NA)
493 | if (!is.na(path)) {
494 | name <- renv_bootstrap_library_root_name(project)
495 | return(file.path(path, name))
496 | }
497 |
498 | prefix <- renv_bootstrap_profile_prefix()
499 | paste(c(project, prefix, "renv/library"), collapse = "/")
500 |
501 | }
502 |
503 | renv_bootstrap_validate_version <- function(version) {
504 |
505 | loadedversion <- utils::packageDescription("renv", fields = "Version")
506 | if (version == loadedversion)
507 | return(TRUE)
508 |
509 | # assume four-component versions are from GitHub; three-component
510 | # versions are from CRAN
511 | components <- strsplit(loadedversion, "[.-]")[[1]]
512 | remote <- if (length(components) == 4L)
513 | paste("rstudio/renv", loadedversion, sep = "@")
514 | else
515 | paste("renv", loadedversion, sep = "@")
516 |
517 | fmt <- paste(
518 | "renv %1$s was loaded from project library, but this project is configured to use renv %2$s.",
519 | "Use `renv::record(\"%3$s\")` to record renv %1$s in the lockfile.",
520 | "Use `renv::restore(packages = \"renv\")` to install renv %2$s into the project library.",
521 | sep = "\n"
522 | )
523 |
524 | msg <- sprintf(fmt, loadedversion, version, remote)
525 | warning(msg, call. = FALSE)
526 |
527 | FALSE
528 |
529 | }
530 |
531 | renv_bootstrap_hash_text <- function(text) {
532 |
533 | hashfile <- tempfile("renv-hash-")
534 | on.exit(unlink(hashfile), add = TRUE)
535 |
536 | writeLines(text, con = hashfile)
537 | tools::md5sum(hashfile)
538 |
539 | }
540 |
541 | renv_bootstrap_load <- function(project, libpath, version) {
542 |
543 | # try to load renv from the project library
544 | if (!requireNamespace("renv", lib.loc = libpath, quietly = TRUE))
545 | return(FALSE)
546 |
547 | # warn if the version of renv loaded does not match
548 | renv_bootstrap_validate_version(version)
549 |
550 | # load the project
551 | renv::load(project)
552 |
553 | TRUE
554 |
555 | }
556 |
557 | renv_bootstrap_profile_load <- function(project) {
558 |
559 | # if RENV_PROFILE is already set, just use that
560 | profile <- Sys.getenv("RENV_PROFILE", unset = NA)
561 | if (!is.na(profile) && nzchar(profile))
562 | return(profile)
563 |
564 | # check for a profile file (nothing to do if it doesn't exist)
565 | path <- file.path(project, "renv/local/profile")
566 | if (!file.exists(path))
567 | return(NULL)
568 |
569 | # read the profile, and set it if it exists
570 | contents <- readLines(path, warn = FALSE)
571 | if (length(contents) == 0L)
572 | return(NULL)
573 |
574 | # set RENV_PROFILE
575 | profile <- contents[[1L]]
576 | if (nzchar(profile))
577 | Sys.setenv(RENV_PROFILE = profile)
578 |
579 | profile
580 |
581 | }
582 |
583 | renv_bootstrap_profile_prefix <- function() {
584 | profile <- renv_bootstrap_profile_get()
585 | if (!is.null(profile))
586 | return(file.path("renv/profiles", profile))
587 | }
588 |
589 | renv_bootstrap_profile_get <- function() {
590 | profile <- Sys.getenv("RENV_PROFILE", unset = "")
591 | renv_bootstrap_profile_normalize(profile)
592 | }
593 |
594 | renv_bootstrap_profile_set <- function(profile) {
595 | profile <- renv_bootstrap_profile_normalize(profile)
596 | if (is.null(profile))
597 | Sys.unsetenv("RENV_PROFILE")
598 | else
599 | Sys.setenv(RENV_PROFILE = profile)
600 | }
601 |
602 | renv_bootstrap_profile_normalize <- function(profile) {
603 |
604 | if (is.null(profile) || profile %in% c("", "default"))
605 | return(NULL)
606 |
607 | profile
608 |
609 | }
610 |
611 | # load the renv profile, if any
612 | renv_bootstrap_profile_load(project)
613 |
614 | # construct path to library root
615 | root <- renv_bootstrap_library_root(project)
616 |
617 | # construct library prefix for platform
618 | prefix <- renv_bootstrap_platform_prefix()
619 |
620 | # construct full libpath
621 | libpath <- file.path(root, prefix)
622 |
623 | # attempt to load
624 | if (renv_bootstrap_load(project, libpath, version))
625 | return(TRUE)
626 |
627 | # load failed; inform user we're about to bootstrap
628 | prefix <- paste("# Bootstrapping renv", version)
629 | postfix <- paste(rep.int("-", 77L - nchar(prefix)), collapse = "")
630 | header <- paste(prefix, postfix)
631 | message(header)
632 |
633 | # perform bootstrap
634 | bootstrap(version, libpath)
635 |
636 | # exit early if we're just testing bootstrap
637 | if (!is.na(Sys.getenv("RENV_BOOTSTRAP_INSTALL_ONLY", unset = NA)))
638 | return(TRUE)
639 |
640 | # try again to load
641 | if (requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) {
642 | message("* Successfully installed and loaded renv ", version, ".")
643 | return(renv::load())
644 | }
645 |
646 | # failed to download or load renv; warn the user
647 | msg <- c(
648 | "Failed to find an renv installation: the project will not be loaded.",
649 | "Use `renv::activate()` to re-initialize the project."
650 | )
651 |
652 | warning(paste(msg, collapse = "\n"), call. = FALSE)
653 |
654 | })
655 |
--------------------------------------------------------------------------------
/renv.lock:
--------------------------------------------------------------------------------
1 | {
2 | "R": {
3 | "Version": "4.1.0",
4 | "Repositories": [
5 | {
6 | "Name": "CRAN",
7 | "URL": "https://packagemanager.rstudio.com/all/latest"
8 | }
9 | ]
10 | },
11 | "Packages": {
12 | "BH": {
13 | "Package": "BH",
14 | "Version": "1.75.0-0",
15 | "Source": "Repository",
16 | "Repository": "RSPM",
17 | "Hash": "e4c04affc2cac20c8fec18385cd14691"
18 | },
19 | "DBI": {
20 | "Package": "DBI",
21 | "Version": "1.1.1",
22 | "Source": "Repository",
23 | "Repository": "RSPM",
24 | "Hash": "030aaec5bc6553f35347cbb1e70b1a17"
25 | },
26 | "MASS": {
27 | "Package": "MASS",
28 | "Version": "7.3-54",
29 | "Source": "Repository",
30 | "Repository": "CRAN",
31 | "Hash": "0e59129db205112e3963904db67fd0dc"
32 | },
33 | "Matrix": {
34 | "Package": "Matrix",
35 | "Version": "1.3-3",
36 | "Source": "Repository",
37 | "Repository": "CRAN",
38 | "Hash": "df57c82e79600601287edfdcef92c2d6"
39 | },
40 | "R6": {
41 | "Package": "R6",
42 | "Version": "2.5.0",
43 | "Source": "Repository",
44 | "Repository": "RSPM",
45 | "Hash": "b203113193e70978a696b2809525649d"
46 | },
47 | "RColorBrewer": {
48 | "Package": "RColorBrewer",
49 | "Version": "1.1-2",
50 | "Source": "Repository",
51 | "Repository": "RSPM",
52 | "Hash": "e031418365a7f7a766181ab5a41a5716"
53 | },
54 | "RSQLite": {
55 | "Package": "RSQLite",
56 | "Version": "2.2.7",
57 | "Source": "Repository",
58 | "Repository": "RSPM",
59 | "Hash": "71f19d00a7736b24492fb26b483bc450"
60 | },
61 | "Rcpp": {
62 | "Package": "Rcpp",
63 | "Version": "1.0.7",
64 | "Source": "Repository",
65 | "Repository": "RSPM",
66 | "Hash": "dab19adae4440ae55aa8a9d238b246bb"
67 | },
68 | "RcppCCTZ": {
69 | "Package": "RcppCCTZ",
70 | "Version": "0.2.9",
71 | "Source": "Repository",
72 | "Repository": "RSPM",
73 | "Hash": "b0826082b7ae7063e42d06856afed2d6"
74 | },
75 | "RcppDate": {
76 | "Package": "RcppDate",
77 | "Version": "0.0.3",
78 | "Source": "Repository",
79 | "Repository": "RSPM",
80 | "Hash": "08cc427d6fe7a63e604cfa11aad31006"
81 | },
82 | "anytime": {
83 | "Package": "anytime",
84 | "Version": "0.3.9",
85 | "Source": "Repository",
86 | "Repository": "RSPM",
87 | "Hash": "74a64813f17b492da9c6afda6b128e3d"
88 | },
89 | "askpass": {
90 | "Package": "askpass",
91 | "Version": "1.1",
92 | "Source": "Repository",
93 | "Repository": "RSPM",
94 | "Hash": "e8a22846fff485f0be3770c2da758713"
95 | },
96 | "attempt": {
97 | "Package": "attempt",
98 | "Version": "0.3.1",
99 | "Source": "Repository",
100 | "Repository": "RSPM",
101 | "Hash": "d7421bb5dfeb2676b9e4a5a60c2fcfd2"
102 | },
103 | "base64enc": {
104 | "Package": "base64enc",
105 | "Version": "0.1-3",
106 | "Source": "Repository",
107 | "Repository": "RSPM",
108 | "Hash": "543776ae6848fde2f48ff3816d0628bc"
109 | },
110 | "bit": {
111 | "Package": "bit",
112 | "Version": "4.0.4",
113 | "Source": "Repository",
114 | "Repository": "RSPM",
115 | "Hash": "f36715f14d94678eea9933af927bc15d"
116 | },
117 | "bit64": {
118 | "Package": "bit64",
119 | "Version": "4.0.5",
120 | "Source": "Repository",
121 | "Repository": "RSPM",
122 | "Hash": "9fe98599ca456d6552421db0d6772d8f"
123 | },
124 | "blob": {
125 | "Package": "blob",
126 | "Version": "1.2.1",
127 | "Source": "Repository",
128 | "Repository": "RSPM",
129 | "Hash": "9addc7e2c5954eca5719928131fed98c"
130 | },
131 | "brew": {
132 | "Package": "brew",
133 | "Version": "1.0-6",
134 | "Source": "Repository",
135 | "Repository": "RSPM",
136 | "Hash": "92a5f887f9ae3035ac7afde22ba73ee9"
137 | },
138 | "brio": {
139 | "Package": "brio",
140 | "Version": "1.1.2",
141 | "Source": "Repository",
142 | "Repository": "RSPM",
143 | "Hash": "2f01e16ff9571fe70381c7b9ae560dc4"
144 | },
145 | "bslib": {
146 | "Package": "bslib",
147 | "Version": "0.2.5.1",
148 | "Source": "Repository",
149 | "Repository": "RSPM",
150 | "Hash": "2f069f3f42847231aef7baa49bed97b0"
151 | },
152 | "cachem": {
153 | "Package": "cachem",
154 | "Version": "1.0.5",
155 | "Source": "Repository",
156 | "Repository": "RSPM",
157 | "Hash": "5346f76a33eb7417812c270b04a5581b"
158 | },
159 | "caldav": {
160 | "Package": "caldav",
161 | "Version": "0.1.0",
162 | "Source": "GitHub",
163 | "RemoteType": "github",
164 | "RemoteHost": "api.github.com",
165 | "RemoteRepo": "caldav",
166 | "RemoteUsername": "petermeissner",
167 | "RemoteRef": "HEAD",
168 | "RemoteSha": "194169af76fda3fe0ae01d36a280190539a079ba",
169 | "Hash": "4a99e12c9a548122f4ca11cd2cf84de2"
170 | },
171 | "calendar": {
172 | "Package": "calendar",
173 | "Version": "0.0.1",
174 | "Source": "Repository",
175 | "Repository": "RSPM",
176 | "Hash": "ab478f470a5203ff9990e42d916005b0"
177 | },
178 | "callr": {
179 | "Package": "callr",
180 | "Version": "3.7.0",
181 | "Source": "Repository",
182 | "Repository": "RSPM",
183 | "Hash": "461aa75a11ce2400245190ef5d3995df"
184 | },
185 | "cli": {
186 | "Package": "cli",
187 | "Version": "2.5.0",
188 | "Source": "Repository",
189 | "Repository": "RSPM",
190 | "Hash": "a94ba44cee3ea571e813721e64184172"
191 | },
192 | "clipr": {
193 | "Package": "clipr",
194 | "Version": "0.7.1",
195 | "Source": "Repository",
196 | "Repository": "RSPM",
197 | "Hash": "ebaa97ac99cc2daf04e77eecc7b781d7"
198 | },
199 | "clock": {
200 | "Package": "clock",
201 | "Version": "0.3.0",
202 | "Source": "Repository",
203 | "Repository": "RSPM",
204 | "Hash": "e2e3e46ab3c217313bc77a9eb364d128"
205 | },
206 | "colorspace": {
207 | "Package": "colorspace",
208 | "Version": "2.0-2",
209 | "Source": "Repository",
210 | "Repository": "RSPM",
211 | "Hash": "6baccb763ee83c0bd313460fdb8b8a84"
212 | },
213 | "colourpicker": {
214 | "Package": "colourpicker",
215 | "Version": "1.1.0",
216 | "Source": "Repository",
217 | "Repository": "RSPM",
218 | "Hash": "fe0cb3d8854c168aef827aeab4b3b473"
219 | },
220 | "commonmark": {
221 | "Package": "commonmark",
222 | "Version": "1.7",
223 | "Source": "Repository",
224 | "Repository": "RSPM",
225 | "Hash": "0f22be39ec1d141fd03683c06f3a6e67"
226 | },
227 | "config": {
228 | "Package": "config",
229 | "Version": "0.3.1",
230 | "Source": "Repository",
231 | "Repository": "RSPM",
232 | "Hash": "31d77b09f63550cee9ecb5a08bf76e8f"
233 | },
234 | "cpp11": {
235 | "Package": "cpp11",
236 | "Version": "0.2.7",
237 | "Source": "Repository",
238 | "Repository": "RSPM",
239 | "Hash": "730eebcc741a5c36761f7d4d0f5e37b8"
240 | },
241 | "crayon": {
242 | "Package": "crayon",
243 | "Version": "1.4.1",
244 | "Source": "Repository",
245 | "Repository": "RSPM",
246 | "Hash": "e75525c55c70e5f4f78c9960a4b402e9"
247 | },
248 | "credentials": {
249 | "Package": "credentials",
250 | "Version": "1.3.0",
251 | "Source": "Repository",
252 | "Repository": "RSPM",
253 | "Hash": "a96728288c75a814c900af9da84387be"
254 | },
255 | "curl": {
256 | "Package": "curl",
257 | "Version": "4.3.1",
258 | "Source": "Repository",
259 | "Repository": "RSPM",
260 | "Hash": "c5e68405893f030f139f6d6675eac675"
261 | },
262 | "data.table": {
263 | "Package": "data.table",
264 | "Version": "1.14.0",
265 | "Source": "Repository",
266 | "Repository": "RSPM",
267 | "Hash": "d1b8b1a821ee564a3515fa6c6d5c52dc"
268 | },
269 | "desc": {
270 | "Package": "desc",
271 | "Version": "1.3.0",
272 | "Source": "Repository",
273 | "Repository": "RSPM",
274 | "Hash": "b6963166f7f10b970af1006c462ce6cd"
275 | },
276 | "devtools": {
277 | "Package": "devtools",
278 | "Version": "2.4.2",
279 | "Source": "Repository",
280 | "Repository": "RSPM",
281 | "Hash": "048d0b914d2cea61f440d35dd3cbddac"
282 | },
283 | "diffobj": {
284 | "Package": "diffobj",
285 | "Version": "0.3.4",
286 | "Source": "Repository",
287 | "Repository": "RSPM",
288 | "Hash": "feb5b7455eba422a2c110bb89852e6a3"
289 | },
290 | "digest": {
291 | "Package": "digest",
292 | "Version": "0.6.27",
293 | "Source": "Repository",
294 | "Repository": "RSPM",
295 | "Hash": "a0cbe758a531d054b537d16dff4d58a1"
296 | },
297 | "dockerfiler": {
298 | "Package": "dockerfiler",
299 | "Version": "0.1.3",
300 | "Source": "Repository",
301 | "Repository": "RSPM",
302 | "Hash": "95d780d9cf3384d97579fc324a8d4c31"
303 | },
304 | "dplyr": {
305 | "Package": "dplyr",
306 | "Version": "1.0.6",
307 | "Source": "Repository",
308 | "Repository": "RSPM",
309 | "Hash": "19e84500b64bc7e589cb1e2550e25832"
310 | },
311 | "ellipsis": {
312 | "Package": "ellipsis",
313 | "Version": "0.3.2",
314 | "Source": "Repository",
315 | "Repository": "RSPM",
316 | "Hash": "bb0eec2fe32e88d9e2836c2f73ea2077"
317 | },
318 | "evaluate": {
319 | "Package": "evaluate",
320 | "Version": "0.14",
321 | "Source": "Repository",
322 | "Repository": "RSPM",
323 | "Hash": "ec8ca05cffcc70569eaaad8469d2a3a7"
324 | },
325 | "fansi": {
326 | "Package": "fansi",
327 | "Version": "0.5.0",
328 | "Source": "Repository",
329 | "Repository": "RSPM",
330 | "Hash": "d447b40982c576a72b779f0a3b3da227"
331 | },
332 | "farver": {
333 | "Package": "farver",
334 | "Version": "2.1.0",
335 | "Source": "Repository",
336 | "Repository": "RSPM",
337 | "Hash": "c98eb5133d9cb9e1622b8691487f11bb"
338 | },
339 | "fastmap": {
340 | "Package": "fastmap",
341 | "Version": "1.1.0",
342 | "Source": "Repository",
343 | "Repository": "RSPM",
344 | "Hash": "77bd60a6157420d4ffa93b27cf6a58b8"
345 | },
346 | "fs": {
347 | "Package": "fs",
348 | "Version": "1.5.0",
349 | "Source": "Repository",
350 | "Repository": "RSPM",
351 | "Hash": "44594a07a42e5f91fac9f93fda6d0109"
352 | },
353 | "generics": {
354 | "Package": "generics",
355 | "Version": "0.1.0",
356 | "Source": "Repository",
357 | "Repository": "RSPM",
358 | "Hash": "4d243a9c10b00589889fe32314ffd902"
359 | },
360 | "gert": {
361 | "Package": "gert",
362 | "Version": "1.3.0",
363 | "Source": "Repository",
364 | "Repository": "RSPM",
365 | "Hash": "56f398846cd40937be6b435a66bd3f37"
366 | },
367 | "ggplot2": {
368 | "Package": "ggplot2",
369 | "Version": "3.3.5",
370 | "Source": "Repository",
371 | "Repository": "RSPM",
372 | "Hash": "d7566c471c7b17e095dd023b9ef155ad"
373 | },
374 | "gh": {
375 | "Package": "gh",
376 | "Version": "1.3.0",
377 | "Source": "Repository",
378 | "Repository": "RSPM",
379 | "Hash": "38c2580abbda249bd6afeec00d14f531"
380 | },
381 | "gitcreds": {
382 | "Package": "gitcreds",
383 | "Version": "0.1.1",
384 | "Source": "Repository",
385 | "Repository": "RSPM",
386 | "Hash": "f3aefccc1cc50de6338146b62f115de8"
387 | },
388 | "glue": {
389 | "Package": "glue",
390 | "Version": "1.4.2",
391 | "Source": "Repository",
392 | "Repository": "RSPM",
393 | "Hash": "6efd734b14c6471cfe443345f3e35e29"
394 | },
395 | "golem": {
396 | "Package": "golem",
397 | "Version": "0.3.1",
398 | "Source": "Repository",
399 | "Repository": "RSPM",
400 | "Hash": "0eaf594de1dcbcd37c6d79806d57b473"
401 | },
402 | "gtable": {
403 | "Package": "gtable",
404 | "Version": "0.3.0",
405 | "Source": "Repository",
406 | "Repository": "RSPM",
407 | "Hash": "ac5c6baf7822ce8732b343f14c072c4d"
408 | },
409 | "here": {
410 | "Package": "here",
411 | "Version": "1.0.1",
412 | "Source": "Repository",
413 | "Repository": "RSPM",
414 | "Hash": "24b224366f9c2e7534d2344d10d59211"
415 | },
416 | "highr": {
417 | "Package": "highr",
418 | "Version": "0.9",
419 | "Source": "Repository",
420 | "Repository": "RSPM",
421 | "Hash": "8eb36c8125038e648e5d111c0d7b2ed4"
422 | },
423 | "hms": {
424 | "Package": "hms",
425 | "Version": "1.1.0",
426 | "Source": "Repository",
427 | "Repository": "RSPM",
428 | "Hash": "e4bf161ccb74a2c1c0e8ac63bbe332b4"
429 | },
430 | "htmltools": {
431 | "Package": "htmltools",
432 | "Version": "0.5.1.1",
433 | "Source": "Repository",
434 | "Repository": "RSPM",
435 | "Hash": "af2c2531e55df5cf230c4b5444fc973c"
436 | },
437 | "htmlwidgets": {
438 | "Package": "htmlwidgets",
439 | "Version": "1.5.3",
440 | "Source": "Repository",
441 | "Repository": "RSPM",
442 | "Hash": "6fdaa86d0700f8b3e92ee3c445a5a10d"
443 | },
444 | "httpgd": {
445 | "Package": "httpgd",
446 | "Version": "1.1.1",
447 | "Source": "GitHub",
448 | "RemoteType": "github",
449 | "RemoteHost": "api.github.com",
450 | "RemoteUsername": "nx10",
451 | "RemoteRepo": "httpgd",
452 | "RemoteRef": "master",
453 | "RemoteSha": "fd72a3cd03ed4f19a9e903a5e1dcda534aea2bef",
454 | "Hash": "a5e9932fe58d74875b5aadca6e87cbfc"
455 | },
456 | "httpuv": {
457 | "Package": "httpuv",
458 | "Version": "1.6.1",
459 | "Source": "Repository",
460 | "Repository": "RSPM",
461 | "Hash": "54344a78aae37bc6ef39b1240969df8e"
462 | },
463 | "httr": {
464 | "Package": "httr",
465 | "Version": "1.4.2",
466 | "Source": "Repository",
467 | "Repository": "RSPM",
468 | "Hash": "a525aba14184fec243f9eaec62fbed43"
469 | },
470 | "ini": {
471 | "Package": "ini",
472 | "Version": "0.3.1",
473 | "Source": "Repository",
474 | "Repository": "RSPM",
475 | "Hash": "6154ec2223172bce8162d4153cda21f7"
476 | },
477 | "isoband": {
478 | "Package": "isoband",
479 | "Version": "0.2.5",
480 | "Source": "Repository",
481 | "Repository": "RSPM",
482 | "Hash": "7ab57a6de7f48a8dc84910d1eca42883"
483 | },
484 | "jquerylib": {
485 | "Package": "jquerylib",
486 | "Version": "0.1.4",
487 | "Source": "Repository",
488 | "Repository": "RSPM",
489 | "Hash": "5aab57a3bd297eee1c1d862735972182"
490 | },
491 | "jsonlite": {
492 | "Package": "jsonlite",
493 | "Version": "1.7.2",
494 | "Source": "Repository",
495 | "Repository": "RSPM",
496 | "Hash": "98138e0994d41508c7a6b84a0600cfcb"
497 | },
498 | "knitr": {
499 | "Package": "knitr",
500 | "Version": "1.33",
501 | "Source": "Repository",
502 | "Repository": "RSPM",
503 | "Hash": "0bc1b5da1b0eb07cd4b727e95e9ff0b8"
504 | },
505 | "labeling": {
506 | "Package": "labeling",
507 | "Version": "0.4.2",
508 | "Source": "Repository",
509 | "Repository": "RSPM",
510 | "Hash": "3d5108641f47470611a32d0bdf357a72"
511 | },
512 | "later": {
513 | "Package": "later",
514 | "Version": "1.2.0",
515 | "Source": "Repository",
516 | "Repository": "RSPM",
517 | "Hash": "b61890ae77fea19fc8acadd25db70aa4"
518 | },
519 | "lattice": {
520 | "Package": "lattice",
521 | "Version": "0.20-44",
522 | "Source": "Repository",
523 | "Repository": "CRAN",
524 | "Hash": "f36bf1a849d9106dc2af72e501f9de41"
525 | },
526 | "lifecycle": {
527 | "Package": "lifecycle",
528 | "Version": "1.0.0",
529 | "Source": "Repository",
530 | "Repository": "RSPM",
531 | "Hash": "3471fb65971f1a7b2d4ae7848cf2db8d"
532 | },
533 | "lubridate": {
534 | "Package": "lubridate",
535 | "Version": "1.7.10",
536 | "Source": "Repository",
537 | "Repository": "RSPM",
538 | "Hash": "1ebfdc8a3cfe8fe19184f5481972b092"
539 | },
540 | "magrittr": {
541 | "Package": "magrittr",
542 | "Version": "2.0.1",
543 | "Source": "Repository",
544 | "Repository": "RSPM",
545 | "Hash": "41287f1ac7d28a92f0a286ed507928d3"
546 | },
547 | "markdown": {
548 | "Package": "markdown",
549 | "Version": "1.1",
550 | "Source": "Repository",
551 | "Repository": "RSPM",
552 | "Hash": "61e4a10781dd00d7d81dd06ca9b94e95"
553 | },
554 | "memoise": {
555 | "Package": "memoise",
556 | "Version": "2.0.0",
557 | "Source": "Repository",
558 | "Repository": "RSPM",
559 | "Hash": "a0bc51650201a56d00a4798523cc91b3"
560 | },
561 | "mgcv": {
562 | "Package": "mgcv",
563 | "Version": "1.8-35",
564 | "Source": "Repository",
565 | "Repository": "CRAN",
566 | "Hash": "89fd8b2ad4a6cb4979b78cf2a77ab503"
567 | },
568 | "mime": {
569 | "Package": "mime",
570 | "Version": "0.11",
571 | "Source": "Repository",
572 | "Repository": "RSPM",
573 | "Hash": "8974a907200fc9948d636fe7d85ca9fb"
574 | },
575 | "miniUI": {
576 | "Package": "miniUI",
577 | "Version": "0.1.1.1",
578 | "Source": "Repository",
579 | "Repository": "RSPM",
580 | "Hash": "fec5f52652d60615fdb3957b3d74324a"
581 | },
582 | "munsell": {
583 | "Package": "munsell",
584 | "Version": "0.5.0",
585 | "Source": "Repository",
586 | "Repository": "RSPM",
587 | "Hash": "6dfe8bf774944bd5595785e3229d8771"
588 | },
589 | "nanotime": {
590 | "Package": "nanotime",
591 | "Version": "0.3.2",
592 | "Source": "Repository",
593 | "Repository": "RSPM",
594 | "Hash": "2ed5b70ae237d914d5b790ed09e34146"
595 | },
596 | "nlme": {
597 | "Package": "nlme",
598 | "Version": "3.1-152",
599 | "Source": "Repository",
600 | "Repository": "CRAN",
601 | "Hash": "35de1ce639f20b5e10f7f46260730c65"
602 | },
603 | "openssl": {
604 | "Package": "openssl",
605 | "Version": "1.4.4",
606 | "Source": "Repository",
607 | "Repository": "RSPM",
608 | "Hash": "f4dbc5a47fd93d3415249884d31d6791"
609 | },
610 | "packrat": {
611 | "Package": "packrat",
612 | "Version": "0.6.0",
613 | "Source": "Repository",
614 | "Repository": "RSPM",
615 | "Hash": "0d6cc4c357e7602bb3eee299f4cfc2a5"
616 | },
617 | "pillar": {
618 | "Package": "pillar",
619 | "Version": "1.6.1",
620 | "Source": "Repository",
621 | "Repository": "RSPM",
622 | "Hash": "8672ae02bd20f6479bce2d06c7ff1401"
623 | },
624 | "pkgbuild": {
625 | "Package": "pkgbuild",
626 | "Version": "1.2.0",
627 | "Source": "Repository",
628 | "Repository": "RSPM",
629 | "Hash": "725fcc30222d4d11ec68efb8ff11a9af"
630 | },
631 | "pkgconfig": {
632 | "Package": "pkgconfig",
633 | "Version": "2.0.3",
634 | "Source": "Repository",
635 | "Repository": "RSPM",
636 | "Hash": "01f28d4278f15c76cddbea05899c5d6f"
637 | },
638 | "pkgload": {
639 | "Package": "pkgload",
640 | "Version": "1.2.1",
641 | "Source": "Repository",
642 | "Repository": "RSPM",
643 | "Hash": "463642747f81879e6752485aefb831cf"
644 | },
645 | "plogr": {
646 | "Package": "plogr",
647 | "Version": "0.2.0",
648 | "Source": "Repository",
649 | "Repository": "RSPM",
650 | "Hash": "09eb987710984fc2905c7129c7d85e65"
651 | },
652 | "png": {
653 | "Package": "png",
654 | "Version": "0.1-7",
655 | "Source": "Repository",
656 | "Repository": "RSPM",
657 | "Hash": "03b7076c234cb3331288919983326c55"
658 | },
659 | "praise": {
660 | "Package": "praise",
661 | "Version": "1.0.0",
662 | "Source": "Repository",
663 | "Repository": "RSPM",
664 | "Hash": "a555924add98c99d2f411e37e7d25e9f"
665 | },
666 | "prettyunits": {
667 | "Package": "prettyunits",
668 | "Version": "1.1.1",
669 | "Source": "Repository",
670 | "Repository": "RSPM",
671 | "Hash": "95ef9167b75dde9d2ccc3c7528393e7e"
672 | },
673 | "processx": {
674 | "Package": "processx",
675 | "Version": "3.5.2",
676 | "Source": "Repository",
677 | "Repository": "RSPM",
678 | "Hash": "0cbca2bc4d16525d009c4dbba156b37c"
679 | },
680 | "progress": {
681 | "Package": "progress",
682 | "Version": "1.2.2",
683 | "Source": "Repository",
684 | "Repository": "RSPM",
685 | "Hash": "14dc9f7a3c91ebb14ec5bb9208a07061"
686 | },
687 | "promises": {
688 | "Package": "promises",
689 | "Version": "1.2.0.1",
690 | "Source": "Repository",
691 | "Repository": "RSPM",
692 | "Hash": "4ab2c43adb4d4699cf3690acd378d75d"
693 | },
694 | "ps": {
695 | "Package": "ps",
696 | "Version": "1.6.0",
697 | "Source": "Repository",
698 | "Repository": "RSPM",
699 | "Hash": "32620e2001c1dce1af49c49dccbb9420"
700 | },
701 | "purrr": {
702 | "Package": "purrr",
703 | "Version": "0.3.4",
704 | "Source": "Repository",
705 | "Repository": "RSPM",
706 | "Hash": "97def703420c8ab10d8f0e6c72101e02"
707 | },
708 | "rappdirs": {
709 | "Package": "rappdirs",
710 | "Version": "0.3.3",
711 | "Source": "Repository",
712 | "Repository": "RSPM",
713 | "Hash": "5e3c5dc0b071b21fa128676560dbe94d"
714 | },
715 | "rcmdcheck": {
716 | "Package": "rcmdcheck",
717 | "Version": "1.3.3",
718 | "Source": "Repository",
719 | "Repository": "RSPM",
720 | "Hash": "ed95895886dab6d2a584da45503555da"
721 | },
722 | "readr": {
723 | "Package": "readr",
724 | "Version": "2.0.0",
725 | "Source": "Repository",
726 | "Repository": "RSPM",
727 | "Hash": "849038f0839134ab35e719a9820005a6"
728 | },
729 | "rematch2": {
730 | "Package": "rematch2",
731 | "Version": "2.1.2",
732 | "Source": "Repository",
733 | "Repository": "RSPM",
734 | "Hash": "76c9e04c712a05848ae7a23d2f170a40"
735 | },
736 | "remotes": {
737 | "Package": "remotes",
738 | "Version": "2.4.0",
739 | "Source": "Repository",
740 | "Repository": "RSPM",
741 | "Hash": "a85ebb35721573b196317b49ddd2dfe4"
742 | },
743 | "renv": {
744 | "Package": "renv",
745 | "Version": "0.13.2",
746 | "Source": "Repository",
747 | "Repository": "RSPM",
748 | "Hash": "079cb1f03ff972b30401ed05623cbe92"
749 | },
750 | "reticulate": {
751 | "Package": "reticulate",
752 | "Version": "1.20",
753 | "Source": "Repository",
754 | "Repository": "RSPM",
755 | "Hash": "30ab0ea8c8d3dd16a3fa06903449bbfb"
756 | },
757 | "rlang": {
758 | "Package": "rlang",
759 | "Version": "0.4.11",
760 | "Source": "Repository",
761 | "Repository": "RSPM",
762 | "Hash": "515f341d3affe0de9e4a7f762efb0456"
763 | },
764 | "rmarkdown": {
765 | "Package": "rmarkdown",
766 | "Version": "2.8",
767 | "Source": "Repository",
768 | "Repository": "RSPM",
769 | "Hash": "f518ba47713f92d0d603eec7c6888faf"
770 | },
771 | "roxygen2": {
772 | "Package": "roxygen2",
773 | "Version": "7.1.1",
774 | "Source": "Repository",
775 | "Repository": "RSPM",
776 | "Hash": "fcd94e00cc409b25d07ca50f7bf339f5"
777 | },
778 | "rprojroot": {
779 | "Package": "rprojroot",
780 | "Version": "2.0.2",
781 | "Source": "Repository",
782 | "Repository": "RSPM",
783 | "Hash": "249d8cd1e74a8f6a26194a91b47f21d1"
784 | },
785 | "rsconnect": {
786 | "Package": "rsconnect",
787 | "Version": "0.8.24",
788 | "Source": "Repository",
789 | "Repository": "RSPM",
790 | "Hash": "5e21fd77eb844fa1ff1d23cb04e1d753"
791 | },
792 | "rstudioapi": {
793 | "Package": "rstudioapi",
794 | "Version": "0.13",
795 | "Source": "Repository",
796 | "Repository": "RSPM",
797 | "Hash": "06c85365a03fdaf699966cc1d3cf53ea"
798 | },
799 | "rversions": {
800 | "Package": "rversions",
801 | "Version": "2.1.1",
802 | "Source": "Repository",
803 | "Repository": "RSPM",
804 | "Hash": "f88fab00907b312f8b23ec13e2d437cb"
805 | },
806 | "sass": {
807 | "Package": "sass",
808 | "Version": "0.4.0",
809 | "Source": "Repository",
810 | "Repository": "RSPM",
811 | "Hash": "50cf822feb64bb3977bda0b7091be623"
812 | },
813 | "scales": {
814 | "Package": "scales",
815 | "Version": "1.1.1",
816 | "Source": "Repository",
817 | "Repository": "RSPM",
818 | "Hash": "6f76f71042411426ec8df6c54f34e6dd"
819 | },
820 | "sessioninfo": {
821 | "Package": "sessioninfo",
822 | "Version": "1.1.1",
823 | "Source": "Repository",
824 | "Repository": "RSPM",
825 | "Hash": "308013098befe37484df72c39cf90d6e"
826 | },
827 | "shiny": {
828 | "Package": "shiny",
829 | "Version": "1.6.0",
830 | "Source": "Repository",
831 | "Repository": "RSPM",
832 | "Hash": "6e3b6ae7fe02b5859e4bb277f218b8ae"
833 | },
834 | "shinyjs": {
835 | "Package": "shinyjs",
836 | "Version": "2.0.0",
837 | "Source": "Repository",
838 | "Repository": "RSPM",
839 | "Hash": "9ddfc91d4280eaa34c2103951538976f"
840 | },
841 | "shinylogs": {
842 | "Package": "shinylogs",
843 | "Version": "0.1.7",
844 | "Source": "Repository",
845 | "Repository": "RSPM",
846 | "Hash": "655ced208106524a1469326972d90d9a"
847 | },
848 | "sourcetools": {
849 | "Package": "sourcetools",
850 | "Version": "0.1.7",
851 | "Source": "Repository",
852 | "Repository": "RSPM",
853 | "Hash": "947e4e02a79effa5d512473e10f41797"
854 | },
855 | "stringi": {
856 | "Package": "stringi",
857 | "Version": "1.6.2",
858 | "Source": "Repository",
859 | "Repository": "RSPM",
860 | "Hash": "9df5e6f9a7fa11b84adf0429961de66a"
861 | },
862 | "stringr": {
863 | "Package": "stringr",
864 | "Version": "1.4.0",
865 | "Source": "Repository",
866 | "Repository": "RSPM",
867 | "Hash": "0759e6b6c0957edb1311028a49a35e76"
868 | },
869 | "sys": {
870 | "Package": "sys",
871 | "Version": "3.4",
872 | "Source": "Repository",
873 | "Repository": "RSPM",
874 | "Hash": "b227d13e29222b4574486cfcbde077fa"
875 | },
876 | "systemfonts": {
877 | "Package": "systemfonts",
878 | "Version": "1.0.2",
879 | "Source": "Repository",
880 | "Repository": "RSPM",
881 | "Hash": "f2e17ba09737e2e7e2ec40fc1f9b6e08"
882 | },
883 | "testthat": {
884 | "Package": "testthat",
885 | "Version": "3.0.2",
886 | "Source": "Repository",
887 | "Repository": "RSPM",
888 | "Hash": "495e0434d9305716b6a87031570ce109"
889 | },
890 | "tibble": {
891 | "Package": "tibble",
892 | "Version": "3.1.2",
893 | "Source": "Repository",
894 | "Repository": "RSPM",
895 | "Hash": "349b40a9f144516d537c875e786ec8b8"
896 | },
897 | "tidyr": {
898 | "Package": "tidyr",
899 | "Version": "1.1.3",
900 | "Source": "Repository",
901 | "Repository": "RSPM",
902 | "Hash": "450d7dfaedde58e28586b854eeece4fa"
903 | },
904 | "tidyselect": {
905 | "Package": "tidyselect",
906 | "Version": "1.1.1",
907 | "Source": "Repository",
908 | "Repository": "RSPM",
909 | "Hash": "7243004a708d06d4716717fa1ff5b2fe"
910 | },
911 | "tinytex": {
912 | "Package": "tinytex",
913 | "Version": "0.32",
914 | "Source": "Repository",
915 | "Repository": "RSPM",
916 | "Hash": "db9a6f2cf147751322d22c9f6647c7bd"
917 | },
918 | "toastui": {
919 | "Package": "toastui",
920 | "Version": "0.1.2.9300",
921 | "Source": "GitHub",
922 | "RemoteType": "github",
923 | "RemoteHost": "api.github.com",
924 | "RemoteUsername": "dreamRs",
925 | "RemoteRepo": "toastui",
926 | "RemoteRef": "master",
927 | "RemoteSha": "89ae28f627c9e7459ab40a8e45f6fe6cfc8e2c4e",
928 | "Hash": "df193d340c7b4174a50f44ebaf49e826"
929 | },
930 | "twitchr": {
931 | "Package": "twitchr",
932 | "Version": "0.1.0",
933 | "Source": "GitHub",
934 | "RemoteType": "github",
935 | "RemoteHost": "api.github.com",
936 | "RemoteUsername": "KoderKow",
937 | "RemoteRepo": "twitchr",
938 | "RemoteRef": "master",
939 | "RemoteSha": "34e99050280663663085ea133ac9f5adeb810253",
940 | "Hash": "d0db733084dbd1a11d5c8b51e859f73c"
941 | },
942 | "tzdb": {
943 | "Package": "tzdb",
944 | "Version": "0.1.1",
945 | "Source": "Repository",
946 | "Repository": "RSPM",
947 | "Hash": "d60ee49eac3f3aaead137af987c92ddb"
948 | },
949 | "usethis": {
950 | "Package": "usethis",
951 | "Version": "2.0.1",
952 | "Source": "Repository",
953 | "Repository": "RSPM",
954 | "Hash": "360e904f9e623286e1a0c6ca0a98c5f6"
955 | },
956 | "utf8": {
957 | "Package": "utf8",
958 | "Version": "1.2.1",
959 | "Source": "Repository",
960 | "Repository": "RSPM",
961 | "Hash": "c3ad47dc6da0751f18ed53c4613e3ac7"
962 | },
963 | "vctrs": {
964 | "Package": "vctrs",
965 | "Version": "0.3.8",
966 | "Source": "Repository",
967 | "Repository": "RSPM",
968 | "Hash": "ecf749a1b39ea72bd9b51b76292261f1"
969 | },
970 | "viridisLite": {
971 | "Package": "viridisLite",
972 | "Version": "0.4.0",
973 | "Source": "Repository",
974 | "Repository": "RSPM",
975 | "Hash": "55e157e2aa88161bdb0754218470d204"
976 | },
977 | "vroom": {
978 | "Package": "vroom",
979 | "Version": "1.5.4",
980 | "Source": "Repository",
981 | "Repository": "RSPM",
982 | "Hash": "1a23013f39e67bb57cbda6f4ddde5470"
983 | },
984 | "waldo": {
985 | "Package": "waldo",
986 | "Version": "0.2.5",
987 | "Source": "Repository",
988 | "Repository": "RSPM",
989 | "Hash": "20c45f1d511a3f730b7b469f4d11e104"
990 | },
991 | "whisker": {
992 | "Package": "whisker",
993 | "Version": "0.4",
994 | "Source": "Repository",
995 | "Repository": "RSPM",
996 | "Hash": "ca970b96d894e90397ed20637a0c1bbe"
997 | },
998 | "withr": {
999 | "Package": "withr",
1000 | "Version": "2.4.2",
1001 | "Source": "Repository",
1002 | "Repository": "RSPM",
1003 | "Hash": "ad03909b44677f930fa156d47d7a3aeb"
1004 | },
1005 | "xfun": {
1006 | "Package": "xfun",
1007 | "Version": "0.23",
1008 | "Source": "Repository",
1009 | "Repository": "RSPM",
1010 | "Hash": "791a57f43c887111490851dcd166d344"
1011 | },
1012 | "xml2": {
1013 | "Package": "xml2",
1014 | "Version": "1.3.2",
1015 | "Source": "Repository",
1016 | "Repository": "RSPM",
1017 | "Hash": "d4d71a75dd3ea9eb5fa28cc21f9585e2"
1018 | },
1019 | "xopen": {
1020 | "Package": "xopen",
1021 | "Version": "1.0.0",
1022 | "Source": "Repository",
1023 | "Repository": "RSPM",
1024 | "Hash": "6c85f015dee9cc7710ddd20f86881f58"
1025 | },
1026 | "xtable": {
1027 | "Package": "xtable",
1028 | "Version": "1.8-4",
1029 | "Source": "Repository",
1030 | "Repository": "RSPM",
1031 | "Hash": "b8acdf8af494d9ec19ccb2481a9b11c2"
1032 | },
1033 | "yaml": {
1034 | "Package": "yaml",
1035 | "Version": "2.2.1",
1036 | "Source": "Repository",
1037 | "Repository": "RSPM",
1038 | "Hash": "2826c5d9efb0a88f657c7a679c7106db"
1039 | },
1040 | "zip": {
1041 | "Package": "zip",
1042 | "Version": "2.2.0",
1043 | "Source": "Repository",
1044 | "Repository": "RSPM",
1045 | "Hash": "c7eef2996ac270a18c2715c997a727c5"
1046 | },
1047 | "zoo": {
1048 | "Package": "zoo",
1049 | "Version": "1.8-9",
1050 | "Source": "Repository",
1051 | "Repository": "RSPM",
1052 | "Hash": "035d1c7c12593038c26fb1c2fd40c4d2"
1053 | }
1054 | }
1055 | }
1056 |
--------------------------------------------------------------------------------