diff --git a/.gitignore b/.gitignore index 24444a33..04314df7 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,8 @@ requal_users.sqlite # gh-pages .quarto wiki/ +tests/test.requal + +# dev stuff +test-iframe.R +wip.R diff --git a/DESCRIPTION b/DESCRIPTION index fd163d1b..ca1d4b33 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: requal Title: Shiny Application for Computer-Assisted Qualitative Data Analysis -Version: 1.1.2.9003 +Version: 1.1.3.9000 Authors@R: c( person(given = "Radim", diff --git a/DEV-NOTES.md b/DEV-NOTES.md index 7b246802..ce34c19d 100644 --- a/DEV-NOTES.md +++ b/DEV-NOTES.md @@ -30,6 +30,7 @@ Instruction for local development of `requal` server version. - [x] add code - [x] merge codes - [x] delete code +- [x] edit code - [x] export codebook #### Categories diff --git a/R/app_server.R b/R/app_server.R index c557b49a..8400cbc0 100644 --- a/R/app_server.R +++ b/R/app_server.R @@ -87,6 +87,16 @@ app_server <- function(input, output, session) { shinyjs::show(selector = ".mfb-component--bl") } }) + + # observe screens + observeEvent(input$analyze_link,{ + updateTabsetPanel(session, "tab_menu", input$analyze_link$tab_menu) + glob$analyze_link <- list( + doc_id = input$analyze_link$doc_id, + segment_start = input$analyze_link$segment_start + ) + }) + # shared mod_download_csv_server("download_csv_ui_1", glob) diff --git a/R/db_logging.R b/R/db_logging.R index 3ea8da6a..d762f5cf 100644 --- a/R/db_logging.R +++ b/R/db_logging.R @@ -80,6 +80,14 @@ log_merge_code_record <- function(con, project_id, from, to, user_id){ data = list(merge_from = from, merge_to = to)) } +log_edit_code_record <- function(con, project_id, changes, user_id){ + log_action(con, + user_id = user_id, + project_id = project_id, + action = "Edit code", + data = changes) +} + log_add_segment_record <- function(con, project_id, segment, user_id){ log_action(con, user_id = user_id, @@ -206,4 +214,5 @@ log_change_user_permission <- function(con, project_id, permission_data, user_id project_id, action = "Change user permission", data = permission_data) -} \ No newline at end of file +} + diff --git a/R/db_startup.R b/R/db_startup.R index 60e34195..4ace6e71 100644 --- a/R/db_startup.R +++ b/R/db_startup.R @@ -1,4 +1,4 @@ -utils::globalVariables(c("sql")) +utils::globalVariables(c("sql", "is_new_quickcode")) db_call <- c( @@ -555,11 +555,12 @@ add_cases_record <- function(pool, project_id, case_df, user_id) { } add_codes_record <- function(pool, project_id, codes_df, user_id) { + res <- DBI::dbWriteTable(pool, "codes", codes_df, append = TRUE, row.names = FALSE) if (res) { written_code_id <- dplyr::tbl(pool, "codes") %>% dplyr::filter(.data$code_name == !!codes_df$code_name, - .data$project_id == !!as.numeric(project_id), + .data$project_id == !!as.integer(project_id), .data$user_id == !!user_id) %>% dplyr::pull(code_id) @@ -574,6 +575,36 @@ add_codes_record <- function(pool, project_id, codes_df, user_id) { } } +add_quickcode_record <- function(pool, project_id, codes_df, user_id) { + + # Make sure column exists to identify new quickcode + db_helper_column(pool, "codes", "is_new_quickcode", "add") + # temporarily write into DB with original code_id + codes_df$is_new_quickcode <- 1 + + res <- DBI::dbWriteTable(pool, "codes", codes_df, append = TRUE, row.names = FALSE) + if (res) { + written_code_id <- dplyr::tbl(pool, "codes") %>% + dplyr::filter(.data$project_id == !!as.integer(project_id), + .data$user_id == !!as.integer(user_id), + is_new_quickcode == 1) %>% + dplyr::pull(code_id) + # remove helper column from DB + db_helper_column(pool, "codes", "is_new_quickcode", "drop") + # just a check we are getting the latest id + written_code_id <- written_code_id[written_code_id == max(written_code_id)] + log_add_code_record(pool, project_id, codes_df %>% + dplyr::mutate( + is_new_quickcode = NULL, + code_id = written_code_id), + user_id + ) + return(written_code_id) + } else { + warning("code not added") + } +} + add_case_doc_record <- function(pool, project_id, case_doc_df, user_id) { res <- DBI::dbWriteTable(pool, "cases_documents_map", case_doc_df, append = TRUE, row.names = FALSE) if (res) { @@ -627,4 +658,5 @@ make_globals <- quote({ existing_projects <- data.frame() } } -}) \ No newline at end of file +}) + diff --git a/R/import_rqda.R b/R/import_rqda.R index 71956c39..0ee82615 100644 --- a/R/import_rqda.R +++ b/R/import_rqda.R @@ -41,8 +41,8 @@ rql_import_rqda <- function(rqda_file, requal_file){ RSQLite::SQLite(), dbname = requal_file ) - - message("Loading data from RQDA") + + rql_message("Loading data from RQDA") # Load Data from RQDA project_df <- dplyr::tbl(rqda_con, "project") %>% dplyr::collect() %>% @@ -117,7 +117,7 @@ rql_import_rqda <- function(rqda_file, requal_file){ dplyr::filter(!is.na(code_id)) # Create requal schema - message("Creating Requal scheme") + rql_message("Creating Requal scheme") create_db_schema(requal_pool) # Import to requal @@ -129,7 +129,7 @@ rql_import_rqda <- function(rqda_file, requal_file){ dplyr::pull(project_id) %>% utils::tail(1) - message("Importing documents") + rql_message("Importing documents") documents_df <- rqda_documents %>% dplyr::mutate(project_id = requal_project_id) purrr::walk(seq_len(nrow(documents_df)), function(x) { @@ -137,7 +137,7 @@ rql_import_rqda <- function(rqda_file, requal_file){ user_id = USER_ID) }) - message("Importing cases") + rql_message("Importing cases") cases_df <- rqda_cases %>% dplyr::mutate(project_id = requal_project_id) purrr::walk(seq_len(nrow(cases_df)), function(x) { @@ -145,7 +145,7 @@ rql_import_rqda <- function(rqda_file, requal_file){ user_id = USER_ID) }) - message("Importing case document map") + rql_message("Importing case document map") case_doc_map <- rqda_case_doc_map %>% dplyr::mutate(project_id = requal_project_id) purrr::walk(seq_len(nrow(case_doc_map)), function(x) { @@ -157,7 +157,7 @@ rql_import_rqda <- function(rqda_file, requal_file){ dplyr::filter(is.na(code_color)) %>% nrow() - message("Importing codes") + rql_message("Importing codes") codes_df <- rqda_codes %>% dplyr::mutate(project_id = requal_project_id, code_color = ifelse(is.na(code_color), @@ -168,7 +168,7 @@ rql_import_rqda <- function(rqda_file, requal_file){ user_id = USER_ID) }) - message("Importing categories") + rql_message("Importing categories") categories_df <- rqda_categories %>% dplyr::mutate(project_id = requal_project_id) purrr::walk(seq_len(nrow(categories_df)), function(x) { @@ -177,7 +177,7 @@ rql_import_rqda <- function(rqda_file, requal_file){ user_id = USER_ID) }) - message("Importing category code mapping") + rql_message("Importing category code mapping") category_code_map <- rqda_category_code_map %>% dplyr::mutate(project_id = requal_project_id) purrr::walk(seq_len(nrow(category_code_map)), function(x) { @@ -186,7 +186,7 @@ rql_import_rqda <- function(rqda_file, requal_file){ user_id = USER_ID) }) - message("Importing segments") + rql_message("Importing segments") segments_df <- rqda_segments %>% dplyr::mutate(project_id = requal_project_id, segment_text = purrr::pmap_chr( @@ -204,7 +204,7 @@ rql_import_rqda <- function(rqda_file, requal_file){ user_id = USER_ID) }) - message("Importing memos") + rql_message("Importing memos") if(!all(is.na(rqda_segments$memo))){ DBI::dbWriteTable(requal_pool, "memos", memos_df %>% dplyr::select(memo_id, text), append = TRUE, row.names = FALSE) diff --git a/R/mod_analysis.R b/R/mod_analysis.R index fb481768..cc724818 100644 --- a/R/mod_analysis.R +++ b/R/mod_analysis.R @@ -200,16 +200,20 @@ mod_rql_button_server( if (nrow(loc$segments_df) > 0) { loc$segments_taglist <- purrr::pmap( list( + loc$segments_df$segment_start, loc$segments_df$segment_text, + loc$segments_df$doc_id, loc$segments_df$doc_name, loc$segments_df$code_name, loc$segments_df$code_color ), ~ format_segments( - segment_text = ..1, - segment_document = ..2, - segment_code = ..3, - segment_color = ..4 + segment_start = ..1, + segment_text = ..2, + segment_document_id = ..3, + segment_document_name = ..4, + segment_code = ..5, + segment_color = ..6 ) ) } diff --git a/R/mod_analysis_utils_analysis.R b/R/mod_analysis_utils_analysis.R index 7de442cd..3bc2c335 100644 --- a/R/mod_analysis_utils_analysis.R +++ b/R/mod_analysis_utils_analysis.R @@ -50,7 +50,7 @@ load_segments_analysis <- function(pool, -format_segments <- function(segment_text, segment_document, segment_code, segment_color) { +format_segments <- function(segment_start, segment_text, segment_document_id, segment_document_name, segment_code, segment_color) { tags$div( @@ -59,8 +59,11 @@ format_segments <- function(segment_text, segment_document, segment_code, segmen tags$blockquote(class = "quote", style = paste0("border-left: 5px solid ", segment_color, "; margin-bottom: 0px !important;")), tags$div( - segment_document %>% - tags$div(class = "segment_badge"), + tags$div(class = "segment_badge", + actionLink(paste0("segment_start-", segment_start), label = segment_document_name, + onclick = paste0("Shiny.setInputValue('analyze_link', {tab_menu: 'Annotate', doc_id: ", segment_document_id,", segment_start: ", segment_start, "}, {priority: 'event'});") + ) + ), segment_code %>% tags$div(class = "segment_badge", style = paste0("background-color: ", segment_color, " !important;")), diff --git a/R/mod_browser.R b/R/mod_browser.R index b0e12c4b..fb5d9582 100644 --- a/R/mod_browser.R +++ b/R/mod_browser.R @@ -170,7 +170,7 @@ mod_browser_server <- function(id, glob){ tibble::tibble( position_start = 0, position_type = "segment_start", - tag_start = "

" + tag_start = "

" ), # content diff --git a/R/mod_categories.R b/R/mod_categories.R index d9998541..5d42855c 100644 --- a/R/mod_categories.R +++ b/R/mod_categories.R @@ -95,7 +95,8 @@ mod_categories_server <- function(id, glob) { }) # Relist categories on codebook changes --------------- - observeEvent(glob$codebook, { + observeEvent(c(glob$codebook, + glob$codebook_observer), { output$categories_ui <- renderUI({ render_categories( id = id, diff --git a/R/mod_codebook.R b/R/mod_codebook.R index 2a3810b4..8d3ccf37 100644 --- a/R/mod_codebook.R +++ b/R/mod_codebook.R @@ -23,10 +23,14 @@ mod_codebook_ui <- function(id) { label = "Create code", icon = "plus" ), + mod_rql_button_ui(ns("code_edit_ui"), + label = "Edit codes", + icon = "edit" + ), mod_rql_button_ui(ns("code_merge_ui"), label = "Merge codes", icon = "compress" - ), + ), mod_rql_button_ui(ns("code_delete_ui"), label = "Delete code", icon = "minus" @@ -65,8 +69,10 @@ mod_codebook_server <- function(id, glob) { observeEvent(c( glob$active_project, input$code_add, + input$code_edit_btn, input$code_merge, - input$code_del_btn + input$code_del_btn, + glob$codebook_observer ), { #---Create code UI -------------- mod_rql_button_server( @@ -84,6 +90,14 @@ mod_codebook_server <- function(id, glob) { glob, permission = "codebook_modify" ) + #---Edit code UI -------------- + mod_rql_button_server( + id = "code_edit_ui", + custom_title = "Edit code", + custom_tagList = edit_code_UI(ns, glob$pool, glob$active_project, glob$user), + glob, + permission = "codebook_modify" + ) #---Delete code UI -------------- mod_rql_button_server( id = "code_delete_ui", @@ -148,6 +162,75 @@ mod_codebook_server <- function(id, glob) { } }) + #---Edit existing code------------------------------------- + # To edit a code, first observe which code is being edited + observeEvent(input$code_to_edit, { + req(input$code_to_edit) + updateTextInput( + session = session, + "edit_code_name", + value = glob$codebook %>% + dplyr::filter(code_id == input$code_to_edit) %>% + dplyr::pull(code_name) + ) + updateTextAreaInput( + session = session, + "edit_code_desc", + value = glob$codebook %>% + dplyr::filter(code_id == input$code_to_edit) %>% + dplyr::pull(code_description) + ) + colourpicker::updateColourInput( + session = session, + "edit_color_pick", + value = glob$codebook %>% + dplyr::filter(code_id == input$code_to_edit) %>% + dplyr::pull(code_color) + ) + }) + # Execute code edit + observeEvent(input$code_edit_btn, { + req(input$code_to_edit) + + # check if code name is unique + code_names <- list_db_codes( + pool = glob$pool, + project_id = glob$active_project, + user = glob$user + ) %>% + dplyr::filter(code_id != input$code_to_edit) %>% # exclude original name from comparison + dplyr::pull(code_name) + + # code must have a name that does not exist yet (unless it stays the same as original) + if (isTruthy(input$edit_code_name) && !input$edit_code_name %in% code_names) { + + # edit code + edit_db_codes( + pool = glob$pool, + active_project = glob$active_project, + user_id = glob$user$user_id, + edit_code_id = input$code_to_edit, + edit_code_name = input$edit_code_name, + edit_code_description = input$edit_code_desc, + edit_code_color = paste0( + "rgb(", + paste( + as.vector( + grDevices::col2rgb( + input$edit_color_pick + ) + ), + collapse = ", " + ), + ")" + ) + ) + } else { + warn_user("Code names must be unique and non-empty.") + } + + }) + #---Delete existing code------------------------------------- observeEvent(input$code_del_btn, { req(input$code_to_del) diff --git a/R/mod_codebook_utils_codebook.R b/R/mod_codebook_utils_codebook.R index 724a1aa8..0d7751b8 100644 --- a/R/mod_codebook_utils_codebook.R +++ b/R/mod_codebook_utils_codebook.R @@ -63,6 +63,53 @@ merge_code_UI <- function(ns, pool, project, user) { } +edit_code_UI <- function(ns, pool, project, user) { + + req(user$data) + + codes <- list_db_codes( + pool, + project_id = project, + user = user + ) + + if(user$data$codebook_other_modify == 0){ + codes <- codes %>% + dplyr::filter(user_id == !!user$user_id) + } + tags$div( + selectizeInput( + ns("code_to_edit"), + label = "Select code to edit", + choices = c("", stats::setNames(codes$code_id, codes$code_name)), + selected = NULL, + multiple = FALSE, + options = list( + closeAfterSelect = "true" + ) + ), + textInput( + ns("edit_code_name"), + label = "Code name" + ) %>% tagAppendAttributes(class = "required"), + textAreaInput( + ns("edit_code_desc"), + label = "Code description" + ), + colourpicker::colourInput( + ns("edit_color_pick"), + label = "Highlight", + value = "white", + showColour = "background", + closeOnClick = TRUE + ), + actionButton(ns("code_edit_btn"), + label = "Edit", + class = "btn-warning" + ) + ) %>% tagAppendAttributes(style = "text-align: left") +} + delete_code_UI <- function(ns, pool, project, user) { req(user$data) @@ -83,7 +130,7 @@ delete_code_UI <- function(ns, pool, project, user) { label = "Select codes to delete", choices = stats::setNames(codes$code_id, codes$code_name), selected = NULL, - multiple = TRUE, + multiple = FALSE, options = list( closeAfterSelect = "true" ) @@ -233,7 +280,6 @@ render_codes <- function(pool, active_project, user) { project_id = active_project, user = user ) - if (nrow(project_codes) == 0) { "No codes have been created." } else { @@ -293,3 +339,29 @@ get_codebook_export_table <- function(glob){ dplyr::group_by(code_id, code_name, code_description) %>% dplyr::summarise(categories = paste0(category_title, collapse = " | ")) } + +# Edit codes ------ + +edit_db_codes <- function(pool, + active_project, + user_id, + edit_code_id, + edit_code_name, + edit_code_description, + edit_code_color) { + + update_code_sql <- glue::glue_sql("UPDATE codes + SET code_name = {edit_code_name}, code_description = {edit_code_description}, code_color = {edit_code_color} + WHERE code_id = {edit_code_id}", .con = pool) + DBI::dbExecute(pool, update_code_sql) + + log_edit_code_record(pool, project_id = active_project, + changes = list( + code_id = edit_code_id, + code_name = edit_code_name, + code_color = edit_code_color, + code_description = edit_code_description), + user_id) + + rql_message(paste("Code", edit_code_name, "was updated.")) +} diff --git a/R/mod_doc_manager.R b/R/mod_doc_manager.R index f81c0b17..cbc8caf8 100644 --- a/R/mod_doc_manager.R +++ b/R/mod_doc_manager.R @@ -216,8 +216,8 @@ mod_doc_manager_server <- function(id, glob) { ) }, error = function(e) { - message("Error in adding document: ", e$message) - "failed" + rql_message(paste0("Error in adding document: ", e$message)) + return("failed") } ) diff --git a/R/mod_document_code.R b/R/mod_document_code.R index 84a85570..261ec1b6 100644 --- a/R/mod_document_code.R +++ b/R/mod_document_code.R @@ -9,43 +9,93 @@ #' @importFrom shiny NS tagList mod_document_code_ui <- function(id) { ns <- NS(id) -tagList( - fluidRow(class = "module_tools", style = "width: 100%", - div(style="display: flex; justify-content: space-between; width: 100%", - selectInput(ns("doc_selector"), - label = "Select a document to code", - choices = "", selected = "" - ), - div(style = "display: flex; align-items: center;", - actionButton(ns("doc_refresh"), - label = "", - icon = icon("sync") - ) %>% tagAppendAttributes(title = "Reload document") - ) - ) -), -fluidRow( - column( - width = 10, - htmlOutput(ns("focal_text")) %>% tagAppendAttributes(class = "scrollable80"), - textOutput(ns("captured_range")), - verbatimTextOutput(ns("printLabel")) + + fluidPage( + tags$head( + tags$script(src = "www/split.min.js"), + tags$script(src = "www/highlight_style.js"), + tags$script(src = "www/document_code_js.js"), + tags$script(HTML(" + document.addEventListener('DOMContentLoaded', (event) => { + Split(['#split-1', '#split-2'], { + sizes: [80, 20], + minSize: [100, 100] + }); + }); +")) ), - column( - width = 2, - tags$b("Codes"), - br(), - actionButton(ns("remove_codes"), - "Remove code", - class = "btn-danger", - width = "100%" + fluidRow( + column( + width = 5, + selectInput( + ns("doc_selector"), + label = "Select a document to code", + choices = "", + selected = "" + ) ), - br(), br(), - uiOutput(ns("code_list")) - ) %>% tagAppendAttributes(class = "scrollable90") - ), - tags$script( - src = "www/document_code_js.js" + column( + width = 5, + div(style = "float: right;", + actionButton( + ns("toggle_style"), + label = "", + icon = icon("highlighter") + ) %>% tagAppendAttributes(title = "Highlight style"), + actionButton( + ns("doc_refresh"), + label = "", + icon = icon("sync") + ) %>% tagAppendAttributes(title = "Reload document") + )), + column( + width = 2, + div(style = "text-align: right;", + actionButton( + ns("code_columns"), + label = "", + icon = icon("table-columns") + ) %>% tagAppendAttributes(title = "Code columns"), + br(), "Selection:", br(), + textOutput(ns("captured_range")) + ) + ) + ), + fluidRow( + style = "height: 90%", + tags$div( + style = "display: flex;", + class = "split", + tags$div( + id = "split-1", + style = "flex-grow: 1; flex-shrink: 1; overflow: auto;", + htmlOutput(ns("focal_text")) %>% tagAppendAttributes(class = "scrollable80") + ), + tags$div( + id = "split-2", + style = "flex-grow: 1; flex-shrink: 1; overflow: auto;", + tags$b("Codes"), + br(), + actionButton( + ns("remove_codes"), + "Remove code", + class = "btn-danger", + width = "100%" + ), + tags$div( + style = "height: calc(1.5em + .75rem + 10px); display: flex; align-items: center; margin-top: 0.5em; margin-bottom: 0.5em;", + tags$div( + style = "flex-grow: 1; height: calc(1.5em + .75rem + 10px);", + tags$iframe( + src = "www/quickcode.html", + style = "height: calc(1.5em + .75rem + 10px); width: 100%; border: none;" + ) + ), + actionButton(ns("quickcode_btn"), "Quick tag", icon = icon("bolt-lightning"), style = "height: calc(1.5em + .75rem + 10px); margin-left: 0px;") + ), + uiOutput(ns("code_list")) %>% tagAppendAttributes(class = "scrollable80") + ) + ) ) ) } @@ -56,139 +106,241 @@ fluidRow( mod_document_code_server <- function(id, glob) { moduleServer(id, function(input, output, session) { ns <- session$ns - loc <- reactiveValues() - - # Selection of documents to code ------------------------------------------ - loc$doc_choices <- NULL + loc$highlight <- "background" + loc$code <- NULL + observeEvent(req(glob$active_project), { + loc$codes_menu_observer <- 0 + loc$code_action_observer <- 0 + loc$text_observer <- 0 + loc$scroll <- 0 + }) + + # Observers - definitions ---- + ## Observe click on coded text ---- + observeEvent(input$clicked_title, { + showNotification(input$clicked_title) + }) + ## Observe choice of highlight style ---- + observeEvent(input$toggle_style, { + loc$highlight <- ifelse(loc$highlight == "underline", "background", "underline") + # Send a message to the client to toggle the style + session$sendCustomMessage("toggleStyle", message = loc$highlight) + }) - # Refresh list of documents when documents are added/removed -------- + ## Observe changes in documents observeEvent(glob$documents, { if (isTruthy(glob$active_project)) { - if(glob$user$data$data_other_view == 1){ + if (glob$user$data$data_other_view == 1) { updateSelectInput( session = session, "doc_selector", choices = c("", glob$documents) - ) - }else{ - visible_docs <- read_visible_docs(glob$pool, glob$active_project, - glob$user$user_id) + ) + } else { + visible_docs <- read_visible_docs( + glob$pool, glob$active_project, + glob$user$user_id + ) updateSelectInput( - session = session, - "doc_selector", + session = session, + "doc_selector", choices = c("", visible_docs) ) } - } }) - - # Displayed text ---------------------------------------------------------- - loc$text <- "" - - observeEvent(c(input$doc_selector, input$doc_refresh), { - req(isTruthy(input$doc_selector)) - loc$text <- load_doc_to_display( - glob$pool, - glob$active_project, - user = glob$user, - input$doc_selector, - loc$code_df$active_codebook, - ns = NS(id) - ) - glob$segments_observer <- glob$segments_observer + 1 - + ## Doc sel or refresh ---- + # Update loc$text when input$doc_selector or input$doc_refresh changes + observeEvent(c(input$doc_selector, + input$doc_refresh), { + req(input$doc_selector) + loc$codes_menu_observer <- loc$codes_menu_observer + 1 # must run first + loc$text_observer <- loc$text_observer + 1 }) - # Render selected text - output$focal_text <- renderText({ - loc$text - }) - - - - # List out available codes ------------------------------------------------ - output$code_list <- renderUI({ - if (isTruthy(glob$active_project)) { - if (isTruthy(glob$codebook)) { - loc$code_df$active_codebook <- glob$codebook - } else { - loc$code_df$active_codebook <- list_db_codes( - glob$pool, - glob$active_project - ) - } + ## Observe refresh ---- + # Update loc$codes_menu when input$doc_refresh or glob$codebook changes + observeEvent(input$doc_refresh, { + loc$codes_menu_observer <- loc$codes_menu_observer + 1 + }) - if (isTruthy(req(input$doc_selector))) { - loc$text <- load_doc_to_display( - glob$pool, - glob$active_project, - user = glob$user, - input$doc_selector, - loc$code_df$active_codebook, - ns = NS(id) - ) - } + ## Code columns observer ----- + observeEvent(input$code_columns, { + shinyjs::toggleClass(id = "codes_menu", "two_columns", asis = TRUE) + }) - purrr::pmap( + ## Codes menu observer ---- + observeEvent(req(loc$codes_menu_observer), { + req(loc$codes_menu_observer > 0) # ignore init value + loc$codebook <- list_db_codes( + glob$pool, + glob$active_project, + user = glob$user + ) + + code_labels <- purrr::pmap( list( - loc$code_df$active_codebook$code_id, - loc$code_df$active_codebook$code_name, - loc$code_df$active_codebook$code_color, - loc$code_df$active_codebook$code_description + loc$codebook$code_id, + loc$codebook$code_name, + loc$codebook$code_color, + loc$codebook$code_description ), ~ generate_coding_tools( ns = ns, code_id = ..1, code_name = ..2, code_color = ..3, - code_desc = ..4) - ) + code_desc = ..4 + )) + + loc$codes_menu <- sortable::rank_list( + input_id = "codes_menu", + css_id = "codes_menu", + labels = code_labels + ) + }) + ## Observe Analyze screen ---- + # Listen to message from Analyze screen + observeEvent(glob$analyze_link, { + updateSelectInput(session = session, "doc_selector", choices = c("", glob$documents), selected = glob$analyze_link$doc_id) + loc$codes_menu_observer <- loc$codes_menu_observer + 1 + loc$text_observer <- loc$text_observer + 1 + loc$scroll <- loc$scroll + 1 + }) + observeEvent(loc$scroll , { + req(isTruthy(loc$scroll)) + session$sendCustomMessage(type = 'scrollToSegment', message = glob$analyze_link$segment_start) + }) - } else { - "" - } + # Render text and codes ---- + output$focal_text <- renderText({ + req(isTruthy(loc$text)) + loc$text + }) + output$code_list <- renderUI({ + req(isTruthy(loc$codes_menu)) + loc$codes_menu }) - # Coding tools ------------------------------------------------------------ - observeEvent(input$selected_code, { - req(input$selected_code, input$tag_position) + # Load text observer ---- + observeEvent(req(loc$text_observer), { + req(loc$text_observer > 0) # ignore init value + loc$text <- load_doc_to_display( + glob$pool, + glob$active_project, + user = glob$user, + ifelse(isTruthy(input$doc_selector), input$doc_selector, glob$analyze_link$doc_id), + loc$codebook, + highlight = loc$highlight, + ns = NS(id) + ) + }) - startOff <- parse_tag_pos(input$tag_position, "start") - endOff <- parse_tag_pos(input$tag_position, "end") + # Coding tools ------------------------------------------------------------ + observeEvent(req(input$selected_code), { + # We need a document and selection positions + req(input$doc_selector) + req(input$tag_position) + # Register code for which action is executed + loc$code <- input$selected_code + # Call code action observer + loc$code_action_observer <- loc$code_action_observer + 1 + }) + ## Codes action observer ---- + # Write code to DB when observer is updated + observeEvent(req(loc$code_action_observer), { + req(loc$code_action_observer > 0) # ignore init value + req(loc$code) + # To execute, we need a document and a selection + startOff <- parse_tag_pos(req(input$tag_position), "start") + endOff <- parse_tag_pos(req(input$tag_position), "end") - if (endOff - startOff > 1 & endOff > startOff) { + if (endOff >= startOff) { write_segment_db( - glob$pool, - glob$active_project, - user_id = glob$user$user_id, - doc_id = input$doc_selector, - code_id = input$selected_code, - startOff, - endOff + glob$pool, + glob$active_project, + user_id = glob$user$user_id, + doc_id = input$doc_selector, + code_id = loc$code, + startOff, + endOff ) + # TODO JS implementation of coding + # code_info <- glob$codebook |> + # dplyr::filter(code_id == input$selected_code) - loc$text <- load_doc_to_display( - glob$pool, - glob$active_project, - user = glob$user, - input$doc_selector, - loc$code_df$active_codebook, - ns = NS(id) - ) + # session$sendCustomMessage(type = 'wrapTextWithBold', message = list( + # startOffset = startOff-1, #convert to javascript + # endOffset = endOff, + # newId = input$selected_code, + # color = code_info$code_color, + # title = code_info$code_name + # )) + + loc$text_observer <- loc$text_observer + 1 glob$segments_observer <- glob$segments_observer + 1 } }) + + # Quick code tools ---- + observeEvent(input$quickcode_btn, { + + # We need a document and selection positions + req(input$doc_selector) + req(input$tag_position) + if (parse_tag_pos(input$tag_position, "start") < parse_tag_pos(input$tag_position, "end")) { + session$sendCustomMessage(type = "getIframeContent", message = list()) + session$sendCustomMessage(type = 'refreshIframe', message = list()) + } else { + rql_message("Missing selected text segment.") + } + + }) + # After quickcode button is pressed, we wait for the quickcode value + observeEvent(req(input$quickcode), { + # check if code name is unique + if (input$quickcode %in% glob$codebook$code_name) { + warn_user("Code names must be unique and non-empty.") + } else { + codes_input_df <- data.frame( + project_id = glob$active_project, + code_name = input$quickcode, + code_description = "", + code_color = "rgb(255,255,0)", + user_id = glob$user$user_id + ) + # Add new quickcode to codes database + # and register the new code_id + loc$code <- add_quickcode_record( + pool = glob$pool, + project_id = glob$active_project, + codes_df = codes_input_df, + user_id = glob$user$user_id + ) + # Refresh codes menu + loc$codes_menu_observer <- loc$codes_menu_observer + 1 + # Execute coding action + loc$code_action_observer <- loc$code_action_observer + 1 + # Refresh text + loc$text_observer <- loc$text_observer + 1 + # Notify codebook screen about new quick code + glob$codebook_observer <- ifelse( + !isTruthy(glob$codebook_observer), + 0, glob$codebook_observer + 1) + # Notify user + rql_message(paste(input$quickcode,"added to codebook.")) + } + }) # Segment removal ---------- observeEvent(input$remove_codes, { req(glob$active_project) - req(isTruthy(input$doc_selector)) - - if(glob$user$data$annotation_other_modify == 0){ + req(input$doc_selector) + + if (glob$user$data$annotation_other_modify == 0) { loc$marked_segments_df <- load_segment_codes_db( - glob$pool, + glob$pool, glob$active_project, user_id = glob$user$user_id, active_doc = input$doc_selector, @@ -196,10 +348,10 @@ mod_document_code_server <- function(id, glob) { input$tag_position, "start" ) - ) - }else{ + ) + } else { loc$marked_segments_df <- load_segment_codes_db( - glob$pool, + glob$pool, glob$active_project, user_id = NULL, active_doc = input$doc_selector, @@ -209,33 +361,25 @@ mod_document_code_server <- function(id, glob) { ) ) } - if (nrow(loc$marked_segments_df) == 0) { NULL } else if (nrow(loc$marked_segments_df) == 1) { delete_segment_codes_db( - glob$pool, - glob$active_project, - user_id = glob$user$user_id, - doc_id = input$doc_selector, - segment_id = loc$marked_segments_df$segment_id - ) - - loc$text <- load_doc_to_display( - glob$pool, - glob$active_project, - user = glob$user, - input$doc_selector, - loc$code_df$active_codebook, - ns = NS(id) + glob$pool, + glob$active_project, + user_id = glob$user$user_id, + doc_id = input$doc_selector, + segment_id = loc$marked_segments_df$segment_id ) + # Refresh text + loc$text_observer <- loc$text_observer + 1 + # Notify analysis screen glob$segments_observer <- glob$segments_observer + 1 - } else { + # Obtain additional input if multiple segments are to be removed showModal( modalDialog( - checkboxGroupInput(ns("codes_to_remove"), label = "", choiceValues = loc$marked_segments_df$segment_id, @@ -258,35 +402,37 @@ mod_document_code_server <- function(id, glob) { } }) + # Multiple segments removal ---- observeEvent(input$remove_codes_select, { if (isTruthy(input$codes_to_remove)) { delete_segment_codes_db( - glob$pool, - glob$active_project, - user_id = glob$user$user_id, - doc_id = input$doc_selector, - segment_id = input$codes_to_remove + glob$pool, + glob$active_project, + user_id = glob$user$user_id, + doc_id = input$doc_selector, + segment_id = input$codes_to_remove ) removeModal() - loc$text <- load_doc_to_display( - glob$pool, - glob$active_project, - user = glob$user, - input$doc_selector, - loc$code_df$active_codebook, - ns = NS(id) - ) + # Refresh text + loc$text_observer <- loc$text_observer + 1 + # Notify analysis screen glob$segments_observer <- glob$segments_observer + 1 } }) - # # Helper (to be commented out in prod): position counter --------------- - + # Helper: position counter --------------- output$captured_range <- renderText({ - input$tag_position + req(isTruthy(input$tag_position)) + splitted_range <- strsplit(input$tag_position, split = "-") + if (splitted_range[[1]][1] == splitted_range[[1]][2]) { + reported_range <- unique(splitted_range[[1]]) + } else { + reported_range <- input$tag_position + } + paste(reported_range) }) - # returns glob$segments_observer + # returns glob$segments_observer and glob$codebook }) } diff --git a/R/mod_document_code_utils_document_code.R b/R/mod_document_code_utils_document_code.R index d0176043..b13ed094 100644 --- a/R/mod_document_code_utils_document_code.R +++ b/R/mod_document_code_utils_document_code.R @@ -75,7 +75,8 @@ load_segments_db <- function(pool, active_project, user, doc_id) { segments <- dplyr::tbl(pool, "segments") %>% dplyr::filter(.data$project_id == as.integer(active_project), .data$doc_id == as.integer(.env$doc_id)) %>% - dplyr::select(code_id, + dplyr::select(segment_id, + code_id, segment_start, segment_end, user_id) %>% @@ -87,7 +88,7 @@ load_segments_db <- function(pool, active_project, user, doc_id) { } return(segments %>% - dplyr::select(code_id, segment_start, segment_end)) + dplyr::select(segment_id, code_id, segment_start, segment_end)) } else {""} } @@ -230,6 +231,7 @@ calculate_code_overlap <- function(raw_segments) { names(vals) <- prevals$name res <- dplyr::tibble( + segment_id = NULL, code_id = NULL, segment_start = NULL, segment_end = NULL @@ -268,8 +270,7 @@ calculate_code_overlap <- function(raw_segments) { dplyr::filter( code_id != "", !is.na(code_id) - ) %>% - dplyr::mutate(segment_id = 0:(dplyr::n() - 1)) + ) } else { res } @@ -282,8 +283,8 @@ load_doc_to_display <- function(pool, user, doc_selector, codebook, + highlight, ns){ - position_type <- position_start <- tag_start <- tag_end <- NULL raw_text <- load_doc_db(pool, active_project, doc_selector) @@ -327,16 +328,20 @@ load_doc_to_display <- function(pool, dplyr::mutate(tag_end = "", tag_start = paste0('')) %>% + '" onclick="Shiny.setInputValue(\'', ns("clicked_title"), '\', this.title, {priority: \'event\'});">')) %>% dplyr::bind_rows( # start doc tibble::tibble(position_start = 0, position_type = "segment_start", - tag_start = "

"), + tag_start = "

"), # content ., # end doc @@ -377,7 +382,7 @@ load_doc_to_display <- function(pool, }else{ df_non_coded <- paste0( - "

", + "

", htmltools::htmlEscape(raw_text) %>% stringr::str_replace_all("[\\n\\r]", @@ -513,3 +518,15 @@ blend_colors <- function(string_id, code_names) { paste0("rgb(", color_mean_string, ")") } + +# highlight/underline ---- + +highlight_style <- function(choice) { + + switch(choice, + underline = '" class="segment" style="padding:0; text-decoration: underline; text-decoration-color:', + background = '" class="segment" style="padding:0; background-color:', + ) + + +} \ No newline at end of file diff --git a/R/mod_memo_utils_memo.R b/R/mod_memo_utils_memo.R index 1549fb31..b54691f3 100644 --- a/R/mod_memo_utils_memo.R +++ b/R/mod_memo_utils_memo.R @@ -133,11 +133,10 @@ export_memos <- function(pool, project) { } + # dropdown2 function ---- dropdownBlock2 <- function (..., id, icon = NULL, title = NULL, badgeStatus = "danger") { - if (!is.null(badgeStatus)) - shinydashboard:::validateStatus(badgeStatus) items <- c(list(...)) dropdownClass <- paste0("dropdown") numItems <- length(items) diff --git a/R/mod_project.R b/R/mod_project.R index b35bafc0..7fab80c1 100644 --- a/R/mod_project.R +++ b/R/mod_project.R @@ -19,16 +19,23 @@ mod_project_ui <- function(id) { tagList( fluidRow( class = "module_tools", - div(mod_rql_button_ui(ns("project_edit_tool"), - label = "Edit project", - icon = "pencil", - inputId = ns("project_edit_menu") - )) %>% tagAppendAttributes(style = "padding-right: 25px;"), - mod_rql_button_ui(ns("project_delete_tool"), + div( + mod_rql_button_ui(ns("project_edit_tool"), + label = "Edit project", + icon = "pencil", + inputId = ns("project_edit_menu") + ) + ) %>% tagAppendAttributes(style = "padding-right: 25px;"), + div(mod_rql_button_ui(ns("project_delete_tool"), label = "Delete project", icon = "trash", inputId = ns("project_delete_menu") - ) + )) %>% tagAppendAttributes(style = "padding-right: 25px;") #, + #mod_rql_button_ui(ns("project_import_tool"), + # label = "Import project", + # icon = "file-import", + # inputId = ns("project_import_menu") + #) ), fluidRow( class = "module_content", @@ -70,15 +77,15 @@ mod_project_server <- function(id, glob) { ns <- session$ns loc <- reactiveValues() loc$edit_observer <- 0 - output$project_name <- renderText({ - loc$project_name - }) - output$project_description <- renderText({ - loc$project_description - }) - output$project_date <- renderText({ - loc$project_date - }) + output$project_name <- renderText({ + loc$project_name + }) + output$project_description <- renderText({ + loc$project_description + }) + output$project_date <- renderText({ + loc$project_date + }) observeEvent(glob$active_project, { loc$project_df <- dplyr::tbl(glob$pool, "projects") %>% dplyr::filter(project_id == local(as.integer(glob$active_project))) @@ -104,33 +111,35 @@ mod_project_server <- function(id, glob) { ), glob, permission = "project_owner" - ) + ) observeEvent(input$project_edit_save, { active_project <- as.integer(local(glob$active_project)) if (input$project_edit_name != loc$project_name) { res <- DBI::dbExecute( - glob$pool, - glue::glue_sql("UPDATE projects + glob$pool, + glue::glue_sql("UPDATE projects SET project_name = {input$project_edit_name} WHERE project_id = {active_project}", .con = glob$pool) ) - updateTextInput(session = session, - "project_edit_name", - value = input$project_edit_name + updateTextInput( + session = session, + "project_edit_name", + value = input$project_edit_name ) } if (input$project_edit_description != loc$project_description) { res <- DBI::dbExecute( - glob$pool, - glue::glue_sql("UPDATE projects + glob$pool, + glue::glue_sql("UPDATE projects SET project_description = {input$project_edit_description} WHERE project_id = {active_project}", .con = glob$pool) ) - updateTextInput(session = session, - "project_edit_description", - value = input$project_edit_description + updateTextInput( + session = session, + "project_edit_description", + value = input$project_edit_description ) } @@ -138,7 +147,6 @@ mod_project_server <- function(id, glob) { dplyr::filter(project_id == local(as.integer(glob$active_project))) shinyjs::removeClass(paste0("sw-content-", ns("project_edit_menu")), "sw-show", asis = TRUE) showNotification("Changes to project were saved.") - }) # Project delete UI ---- diff --git a/R/rql_logo.R b/R/rql_logo.R new file mode 100644 index 00000000..949f3e66 --- /dev/null +++ b/R/rql_logo.R @@ -0,0 +1,3 @@ +rql_logo <- function(){ + "data:image/png;base64,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" +} \ No newline at end of file diff --git a/R/utils.R b/R/utils.R index fdd63e80..776dae18 100644 --- a/R/utils.R +++ b/R/utils.R @@ -50,6 +50,11 @@ utils::globalVariables(c("project_name", "credentials" )) +# dummy function for satisfying checks (getting rid of Note on not used imports) +dummy <- function(){ + dbplyr::sql + RPostgreSQL::dbConnect +} set_dashboard_body <- function() { @@ -334,6 +339,15 @@ warn_user <- function(warning) { warning)) } +# send message to interactive or Shiny session +rql_message <- function(msg) { + if (shiny::isRunning()){ + showNotification(msg) + } else { + message(msg) + } +} + # check permission to modify permissions check_modify_permission <- function(permission, msg) { @@ -394,6 +408,38 @@ rql_button_UI <- function(inputId, label, class = NULL) { tagAppendAttributes(style = "text-align: left;") } -rql_logo <- function(){ - "data:image/png;base64,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" + + + +db_helper_column <- function(pool, table, column, action){ + + check_colnames <- colnames(dplyr::tbl(pool, table)) + query <- switch(action, + "add" = glue::glue_sql(" + ALTER TABLE {`table`} + ADD COLUMN {`column`} INTEGER; + ", .con = pool), + "drop" = glue::glue_sql(" + ALTER TABLE {`table`} + DROP COLUMN {`column`} + ", .con = pool) + ) + if (!column %in% check_colnames && action == "add") { + res <- DBI::dbExecute(pool, query) + } else if (column %in% check_colnames && action == "drop"){ + res <- DBI::dbExecute(pool, query) + } else { + NULL + } } + +db_update_value <- function(pool, table, col_val, by_col_val){ + # col_val can be a list - list(c(col=1), c(col=2)) + query <- purrr::map(col_val, .f = function(x){ + glue::glue_sql("UPDATE {table} + SET {names(x)} = {x} + WHERE {names(by_col_val)} = {by_col_val}", .con = pool)}) + + res <- purrr::map(query, ~tryCatch({DBI::dbExecute(pool, .x)})) + +} \ No newline at end of file diff --git a/dev/run_dev.R b/dev/run_dev.R index 0608b98c..ea8f1683 100644 --- a/dev/run_dev.R +++ b/dev/run_dev.R @@ -8,32 +8,31 @@ golem::detach_all_attached() # Document and reload your package golem::document_and_reload() -# Run the application -# (run_app( -# mode = "server", -# dbname = "requal", -# dbhost = "localhost", -# dbusername = "requal_admin", -# dbpassword = "test", -# credentials_path = "requal_users.sqlite", -# credentials_pass = "test", -# options = list("launch.browser") -# )) - - (run_app( - mode = "local", + mode = "server", + dbname = "requal", + dbhost = "localhost", + dbusername = "requal_admin", + dbpassword = "test", + credentials_path = "requal_users.sqlite", + credentials_pass = "test", options = list("launch.browser") -)) + )) -(run_app( - mode = "local_test", - dbname = "tests/test.requal", - # dbhost = "localhost", - # dbusername = "requal_admin", - # dbpassword = "test", - # credentials_path = "requal_users.sqlite", - # credentials_pass = "test", - options = list("launch.browser") -)) +# (run_app( +# mode = "local", +# options = list("launch.browser") +#)) + + +# (run_app( +# mode = "local_test", +# dbname = "tests/test.requal", +# # dbhost = "localhost", +# # dbusername = "requal_admin", +# # dbpassword = "test", +# # credentials_path = "requal_users.sqlite", +# # credentials_pass = "test", +# options = list("launch.browser") +# )) diff --git a/inst/app/www/custom.css b/inst/app/www/custom.css index 4fb3fa48..8eeb9306 100644 --- a/inst/app/www/custom.css +++ b/inst/app/www/custom.css @@ -33,8 +33,14 @@ .code-button:hover { overflow: visible; + white-space: normal; + text-overflow: clip; +} +.code-button:popover { + overflow: initial; + white-space: initial; + text-overflow: initial; } - .quote { font-size: medium; line-height: 1; @@ -60,12 +66,14 @@ p.docpar { } .br { -height: 0px; -width: 0px; + height: 0px; + width: 0px; } b.segment { -font-weight: normal; + font-weight: normal; + text-decoration-thickness: 5px !important; + text-underline-offset: 3px !important; } @@ -150,3 +158,60 @@ width: 100% !important; .logo { background-color: rgb(63, 79, 144) !important; } +.split { + display: flex; + flex-direction: row; +} + +.split > #split-1 { + padding-top: 5px !important; + padding-left: 10px !important; + padding-right: 10px !important; + background-color: #ffffff !important; +} +.gutter { + background-color: #eee; + background-repeat: no-repeat; + background-position: 50%; +} + +.gutter.gutter-horizontal { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg=='); + cursor: col-resize; +} + +#rank-list-codes_menu { +background-color: transparent; +border: none; +padding: 0px; +margin-left: -0.3em; +margin-right: -0.3em; +display: flex; +} + + +.docpar::selection { + background: #ffffcc; + color: black; + text-decoration-line: underline; + text-decoration-style: dotted; + text-decoration-color: red; +} + +.docpar b::selection { + background: rgb(255, 255, 204, 0.75); + color: black; + text-decoration-line: underline; + text-decoration-style: dotted; + text-decoration-color: red; +} + +.quickcode { + height: calc(1.5em + .75rem + 2px); +} + + /* codes menu column layout */ + .two_columns { + display: grid; + grid-template-columns: 1fr 1fr; /* Creates two columns of equal width */ + } diff --git a/inst/app/www/document_code_js.js b/inst/app/www/document_code_js.js index 1e87e921..06a8db83 100644 --- a/inst/app/www/document_code_js.js +++ b/inst/app/www/document_code_js.js @@ -69,25 +69,57 @@ document.addEventListener('mouseup', function () { } var tag_position_value = startOffset.toString() + '-' + endOffset.toString(); - + console.log("tag_position" + tag_position_value) Shiny.setInputValue('document_code_ui_1-tag_position', tag_position_value); } }, false); }) -// Auxiliary function to add highlight in the browser + +Shiny.addCustomMessageHandler('getIframeContent', function(message) { + var iframe = document.getElementsByTagName('iframe')[0]; + var res = iframe.contentDocument.getElementById('quickCodeInput'); + var quickodeValue = res.dataset.quickode; + Shiny.setInputValue('document_code_ui_1-quickcode', quickodeValue); +}); + +Shiny.addCustomMessageHandler('refreshIframe', function(message) { + var iframe = document.getElementsByTagName('iframe')[0]; + iframe.src = iframe.src; +}); + +function findScrollElement(message) { + let targetStart = parseInt(message, 10); + console.log("Target Start:", targetStart); + let segments = document.querySelectorAll('article .segment'); + let segmentStartValues = Array.from(segments).map(el => parseInt(el.dataset.segment_start, 10)); + let index = segmentStartValues.findIndex(value => value => targetStart); + return segments[index]; // This could be undefined if no matching segment is found +} $( document ).ready(function() { - Shiny.addCustomMessageHandler('highlight', function(arg_color) { - - var selection = window.getSelection().getRangeAt(0); - if(window.getSelection().baseNode.parentNode.id != "document_code_ui_1-focal_text") return; - var selectedText = selection.extractContents(); - var mark = document.createElement("mark"); - mark.style.background = arg_color; - mark.appendChild(selectedText); - selection.insertNode(mark); - }) + +Shiny.addCustomMessageHandler('scrollToSegment', function(message) { + let el = findScrollElement(message); + if (el) { // Check if the element exists before trying to scroll into view + console.log("Scrolling to element:", el); + scrollToElementWithinContainer(el); + } else { + console.log("No element found to scroll to for message:", message); + } +}); }); +function scrollToElementWithinContainer(targetSelected) { + let container = document.querySelector('#document_code_ui_1-focal_text'); + let target = targetSelected; + + if (container && target) { + let targetPosition = target.getBoundingClientRect().top; + let containerPosition = container.getBoundingClientRect().top; + let scrollPosition = targetPosition - containerPosition + container.scrollTop; + + container.scrollTo({ top: scrollPosition, behavior: 'smooth' }); + } +} diff --git a/inst/app/www/highlight_style.js b/inst/app/www/highlight_style.js new file mode 100644 index 00000000..7d7a7626 --- /dev/null +++ b/inst/app/www/highlight_style.js @@ -0,0 +1,20 @@ +$( document ).ready(function(event) { + // Listen for messages of type 'toggleStyle' + Shiny.addCustomMessageHandler('toggleStyle', function(mode) { + // Loop over all elements with the class 'segment' + $('.segment').each(function() { + var higlight_tag = $(this); + var original_background_color = higlight_tag.data('color'); // Get the original color from the data-color attribute + // Determine the new mode and apply styles accordingly + if (mode === 'underline') { + higlight_tag.css('background-color', 'transparent'); // Clear background color + higlight_tag.css('text-decoration', 'underline'); // Apply underline + higlight_tag.css('text-decoration-color', original_background_color); // Use the original color for underline + } else if (mode === 'background') { + higlight_tag.css('background-color', original_background_color); // Apply original color as background color + higlight_tag.css('text-decoration', ''); // Remove underline + higlight_tag.css('text-decoration-color', ''); // Remove underline color + } + }); + }); +}); diff --git a/inst/app/www/highlight_tool.js b/inst/app/www/highlight_tool.js new file mode 100644 index 00000000..47e0c909 --- /dev/null +++ b/inst/app/www/highlight_tool.js @@ -0,0 +1,165 @@ + +Shiny.addCustomMessageHandler('wrapTextWithBold', function(message) { + const startOffset = message.startOffset; + const endOffset = message.endOffset; + const newId = message.newId; + const color = message.color; + const title = message.title; + wrapTextWithBold(startOffset, endOffset, newId, color, title); + }); + + + function wrapTextWithBold(startOffset, endOffset, newId, color, title) { + const container = document.querySelector('#article'); + if (!container) { + console.error("Container not found"); + return; + } + + let globalOffset = 0; + + Array.from(container.childNodes).forEach((p) => { + if (p.nodeType === Node.ELEMENT_NODE && p.tagName.toLowerCase() === 'p') { + let plainText = ''; + let bTagPositions = []; + Array.from(p.childNodes).forEach(child => { + if (child.nodeType === Node.ELEMENT_NODE && child.tagName.toLowerCase() === 'b') { + bTagPositions.push({pos: plainText.length, type: 'start', id: child.id, color: child.getAttribute('data-color'), title: child.title}); + plainText += child.textContent; + bTagPositions.push({pos: plainText.length, type: 'end', id: child.id}); + } else if (child.nodeType === Node.TEXT_NODE) { + plainText += child.textContent; + } + }); + + if (globalOffset <= endOffset && globalOffset + plainText.length >= startOffset) { + const paragraphStart = Math.max(startOffset - globalOffset, 0); + const paragraphEnd = Math.min(endOffset - globalOffset, plainText.length); + bTagPositions.push({pos: paragraphStart, type: 'start', id: newId, color: color, title: title}); + bTagPositions.push({pos: paragraphEnd, type: 'end', id: newId}); + } + + bTagPositions.sort((a, b) => a.pos - b.pos || (a.type === 'end' ? -1 : 1)); + + let finalBTagPositions = []; + let openTags = []; + let openColors = []; + let openTitles = []; + for (let i = 0; i < bTagPositions.length - 1; i++) { + if (bTagPositions[i].type === 'start') { + openTags.push(bTagPositions[i].id); + openColors.push(bTagPositions[i].color); + openTitles.push(bTagPositions[i].title); + } else { + const index = openTags.indexOf(bTagPositions[i].id); + openTags.splice(index, 1); + openColors.splice(index, 1); + openTitles.splice(index, 1); + } + if (bTagPositions[i].pos !== bTagPositions[i+1].pos && openTags.length > 0) { + let avgColor = "rgb(0,0,0)"; + if (openColors.length > 0) { + let r = 0, g = 0, b = 0; + openColors.forEach(color => { + let match = color.match(/\d+/g); + r += parseInt(match[0]); + g += parseInt(match[1]); + b += parseInt(match[2]); + }); + r = Math.round(r / openColors.length); + g = Math.round(g / openColors.length); + b = Math.round(b / openColors.length); + avgColor = `rgb(${r},${g},${b})`; + } + finalBTagPositions.push({start: bTagPositions[i].pos, end: bTagPositions[i+1].pos, id: openTags.join('+'), color: avgColor, title: openTitles.join(' | ')}); + } + } + + p.innerHTML = ''; + let currentOffset = 0; + finalBTagPositions.forEach((position) => { + if (position.start !== position.end) { + const before = document.createTextNode(plainText.slice(currentOffset, position.start)); + const middle = document.createElement('b'); + middle.id = position.id; + middle.title = position.title; + middle.setAttribute('data-color', position.color); + middle.classList.add('segment'); + middle.textContent = plainText.slice(position.start, position.end); + middle.setAttribute('onclick', "Shiny.setInputValue('document_code_ui_1-clicked_title', this.title, {priority: 'event'});"); + p.appendChild(before); + p.appendChild(middle); + currentOffset = position.end; + } + }); + const after = document.createTextNode(plainText.slice(currentOffset)); + p.appendChild(after); + + globalOffset += plainText.length; + } + }); + } + // // Auxiliary function to add highlight in the browser + // $( document ).ready(function() { + // Shiny.addCustomMessageHandler('highlight', function(message) { + // var startOff = message.startOff; + // var endOff = message.endOff; + // console.log(startOff) + // console.log(endOff) + // wrapSelection(startOff, endOff); + // }) + // }); + + // function wrapSelection(startOffset, endOffset) { + // let currentOffset = 0; + + // function traverse(node) { + // if (node.nodeType === Node.TEXT_NODE) { + // const textLength = node.textContent.length; + + // // If the startOffset is within this text node + // if (startOffset >= currentOffset && startOffset < currentOffset + textLength) { + // const start = startOffset - currentOffset; + // const before = node.textContent.slice(0, start); + // const after = node.textContent.slice(start); + // const newNode = document.createElement('b'); + // newNode.textContent = after; + // node.textContent = before; + // node.parentNode.insertBefore(newNode, node.nextSibling); + // } + + // // If the endOffset is within this text node + // if (endOffset > currentOffset && endOffset <= currentOffset + textLength) { + // const end = endOffset - currentOffset; + // const boldNode = node.nextSibling; + // const before = boldNode.textContent.slice(0, end); + // const after = boldNode.textContent.slice(end); + // const newNode = document.createTextNode(after); + // boldNode.textContent = before; + // boldNode.parentNode.insertBefore(newNode, boldNode.nextSibling); + // } + + // currentOffset += textLength; + // } else { + // for (let child of node.childNodes) { + // traverse(child); + // } + // } + // } + + // traverse(document.querySelector('article')); + // } + + //

+ //

Text of paragraph 1

+ //

Text of paragraph 2

+ //

Text of paragraph 3

+ //

Text of paragraph 4

+ //
+ + //
+ //

Text of paragraph 1

+ //

Text of paragraph 2

+ //

Text of paragraph 3

+ //

Text of paragraph 4

+ //
\ No newline at end of file diff --git a/inst/app/www/quickcode.html b/inst/app/www/quickcode.html new file mode 100644 index 00000000..cbc20b6f --- /dev/null +++ b/inst/app/www/quickcode.html @@ -0,0 +1,34 @@ + + + + + + + \ No newline at end of file diff --git a/inst/app/www/split.min.js b/inst/app/www/split.min.js new file mode 100644 index 00000000..cc0a2a43 --- /dev/null +++ b/inst/app/www/split.min.js @@ -0,0 +1,267 @@ +/*! Split.js - v1.6.0 */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self).Split=t()}(this,(function(){ + "use strict"; + var e="undefined"!=typeof window?window:null, + t=null===e, + n=t?void 0:e.document, + i=function(){return!1}, + r=t?"calc":["","-webkit-","-moz-","-o-"].filter((function(e){ + var t=n.createElement("div"); + return t.style.cssText="width:"+e+"calc(9px)", + !!t.style.length + })).shift()+"calc", + s=function(e){return"string"==typeof e||e instanceof String}, + o=function(e){if(s(e)){ + var t=n.querySelector(e); + if(!t)throw new Error("Selector "+e+" did not match a DOM element"); + return t + } + return e + }, + a=function(e,t,n){var i=e[t]; + return void 0!==i?i:n + }, + u=function(e,t,n,i){ + if(t){ + if("end"===i)return 0; + if("center"===i)return e/2 + }else if(n){ + if("start"===i)return 0; + if("center"===i)return e/2 + } + return e + }, + l=function(e,t){var i=n.createElement("div"); + return i.className="gutter gutter-"+t,i + }, + c=function(e,t,n){var i={}; + return s(t)?i[e]=t:i[e]=r+"("+t+"% - "+n+"px)",i + }, + h=function(e,t){var n; + return(n={})[e]=t+"px",n + }; + return function(r,s){ + if(void 0===s&&(s={}),t)return{}; + var d,f,v,m,g,p,y=r; + Array.from&&(y=Array.from(y)); + var z=o(y[0]).parentNode, + b=getComputedStyle?getComputedStyle(z):null, + E=b?b.flexDirection:null, + S=a(s,"sizes")||y.map((function(){return 100/y.length})), + L=a(s,"minSize",100), + _=Array.isArray(L)?L:y.map((function(){return L})), + w=a(s,"expandToMin",!1), + k=a(s,"gutterSize",10), + x=a(s,"gutterAlign","center"), + C=a(s,"snapOffset",30), + M=a(s,"dragInterval",1), + U=a(s,"direction","horizontal"), + O=a(s,"cursor","horizontal"===U?"col-resize":"row-resize"), + D=a(s,"gutter",l), + A=a(s,"elementStyle",c), + B=a(s,"gutterStyle",h); + function j(e,t,n,i){ + var r=A(d,t,n,i); + Object.keys(r).forEach((function(t){ + e.style[t]=r[t] + })) + } + function F(){ + return p.map((function(e){ + return e.size + })) + } + function R(e){ + return"touches"in e?e.touches[0][f]:e[f] + } + function T(e){ + var t=p[this.a], + n=p[this.b], + i=t.size+n.size; + t.size=e/this.size*i, + n.size=i-e/this.size*i, + j(t.element,t.size,this._b,t.i), + j(n.element,n.size,this._c,n.i) + } + function N(e){ + var t,n=p[this.a], + r=p[this.b]; + this.dragging&&(t=R(e)-this.start+(this._b-this.dragOffset), + M>1&&(t=Math.round(t/M)*M), + t<=n.minSize+C+this._b?t=n.minSize+this._b:t>=this.size-(r.minSize+C+this._c)&&(t=this.size-(r.minSize+this._c)), + T.call(this,t), + a(s,"onDrag",i)()) + } + function q(){ + var e=p[this.a].element, + t=p[this.b].element, + n=e.getBoundingClientRect(), + i=t.getBoundingClientRect(); + this.size=n[d]+i[d]+this._b+this._c, + this.start=n[v], + this.end=n[m] + } + function H(e){ + var t=function(e){ + if(!getComputedStyle)return null; + var t=getComputedStyle(e); + if(!t)return null; + var n=e[g]; + return 0===n?null:n-="horizontal"===U?parseFloat(t.paddingLeft)+parseFloat(t.paddingRight):parseFloat(t.paddingTop)+parseFloat(t.paddingBottom) + }(z); + if(null===t)return e; + if(_.reduce((function(e,t){return e+t}),0)>t)return e; + var n=0,i=[],r=e.map((function(r,s){ + var o=t*r/100, + a=u(k,0===s,s===e.length-1,x), + l=_[s]+a; + return o0&&i[r]-n>0){ + var o=Math.min(n,i[r]-n); + n-=o,s=e-o + } + return s/t*100 + })) + } + function I(){ + var t=p[this.a].element, + r=p[this.b].element; + this.dragging&&a(s,"onDragEnd",i)(F()), + this.dragging=!1, + e.removeEventListener("mouseup",this.stop), + e.removeEventListener("touchend",this.stop), + e.removeEventListener("touchcancel",this.stop), + e.removeEventListener("mousemove",this.move), + e.removeEventListener("touchmove",this.move), + this.stop=null, + this.move=null, + t.removeEventListener("selectstart",i), + t.removeEventListener("dragstart",i), + r.removeEventListener("selectstart",i), + r.removeEventListener("dragstart",i), + t.style.userSelect="", + t.style.webkitUserSelect="", + t.style.MozUserSelect="", + t.style.pointerEvents="", + r.style.userSelect="", + r.style.webkitUserSelect="", + r.style.MozUserSelect="", + r.style.pointerEvents="", + this.gutter.style.cursor="", + this.parent.style.cursor="", + n.body.style.cursor="" + } + function W(t){ + if(!("button"in t)||0===t.button){ + var r=p[this.a].element, + o=p[this.b].element; + this.dragging||a(s,"onDragStart",i)(F()), + t.preventDefault(), + this.dragging=!0, + this.move=N.bind(this), + this.stop=I.bind(this), + e.addEventListener("mouseup",this.stop), + e.addEventListener("touchend",this.stop), + e.addEventListener("touchcancel",this.stop), + e.addEventListener("mousemove",this.move), + e.addEventListener("touchmove",this.move), + r.addEventListener("selectstart",i), + r.addEventListener("dragstart",i), + o.addEventListener("selectstart",i), + o.addEventListener("dragstart",i), + r.style.userSelect="none", + r.style.webkitUserSelect="none", + r.style.MozUserSelect="none", + r.style.pointerEvents="none", + o.style.userSelect="none", + o.style.webkitUserSelect="none", + o.style.MozUserSelect="none", + o.style.pointerEvents="none", + this.gutter.style.cursor=O, + this.parent.style.cursor=O, + n.body.style.cursor=O, + q.call(this), + this.dragOffset=R(t)-this.end + } + } + "horizontal"===U?(d="width",f="clientX",v="left",m="right",g="clientWidth"):"vertical"===U&&(d="height",f="clientY",v="top",m="bottom",g="clientHeight"), + S=H(S); + var X=[]; + function Y(e){ + var t=e.i===X.length,n=t?X[e.i-1]:X[e.i]; + q.call(n); + var i=t?n.size-e.minSize-n._c:e.minSize+n._b; + T.call(n,i) + } + return(p=y.map((function(e,t){ + var n,i={element:o(e),size:S[t],minSize:_[t],i:t}; + if(t>0&&((n={a:t-1,b:t,dragging:!1,direction:U,parent:z})._b=u(k,t-1==0,!1,x), + n._c=u(k,!1,t===y.length-1,x), + "row-reverse"===E||"column-reverse"===E)){ + var r=n.a; + n.a=n.b, + n.b=r + } + if(t>0){ + var s=D(t,U,i.element); + !function(e,t,n){ + var i=B(d,t,n); + Object.keys(i).forEach((function(t){ + e.style[t]=i[t] + })) + }(s,k,t), + n._a=W.bind(n), + s.addEventListener("mousedown",n._a), + s.addEventListener("touchstart",n._a), + z.insertBefore(s,i.element), + n.gutter=s + } + return j(i.element,i.size,u(k,0===t,t===y.length-1,x),t), + t>0&&X.push(n), + i + }))).forEach((function(e){ + var t=e.element.getBoundingClientRect()[d]; + t0){ + var i=X[n-1], + r=p[i.a], + s=p[i.b]; + r.size=t[n-1], + s.size=e, + j(r.element,r.size,i._b,i.i), + j(s.element,s.size,i._c,s.i) + } + })) + }, + getSizes:F, + collapse:function(e){ + Y(p[e]) + }, + destroy:function(e,t){ + X.forEach((function(n){ + if(!0!==t?n.parent.removeChild(n.gutter):(n.gutter.removeEventListener("mousedown",n._a), + n.gutter.removeEventListener("touchstart",n._a)), + !0!==e){ + var i=A(d,n.a.size,n._b); + Object.keys(i).forEach((function(e){ + p[n.a].element.style[e]="", + p[n.b].element.style[e]="" + })) + } + })) + }, + parent:z, + pairs:X + } + } +})); + + diff --git a/inst/refi.xsd b/inst/refi.xsd new file mode 100644 index 00000000..a2552ff6 --- /dev/null +++ b/inst/refi.xsd @@ -0,0 +1,501 @@ + + + + + + + + + + This element MUST be conveyed as the root element in any instance document based on this Schema expression + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inst/test_app/tests/testthat/_snaps/app-create-codes/createcode-001_.png b/inst/test_app/tests/testthat/_snaps/app-create-codes/createcode-001_.png index d7adc8b2..a7775a45 100644 Binary files a/inst/test_app/tests/testthat/_snaps/app-create-codes/createcode-001_.png and b/inst/test_app/tests/testthat/_snaps/app-create-codes/createcode-001_.png differ diff --git a/inst/test_app/tests/testthat/_snaps/app-create-codes/createcode-002_.png b/inst/test_app/tests/testthat/_snaps/app-create-codes/createcode-002_.png index 230543b0..70f732ed 100644 Binary files a/inst/test_app/tests/testthat/_snaps/app-create-codes/createcode-002_.png and b/inst/test_app/tests/testthat/_snaps/app-create-codes/createcode-002_.png differ diff --git a/inst/test_app/tests/testthat/_snaps/app-delete-code/requaltest-001_.png b/inst/test_app/tests/testthat/_snaps/app-delete-code/requaltest-001_.png index daf82135..66236e12 100644 Binary files a/inst/test_app/tests/testthat/_snaps/app-delete-code/requaltest-001_.png and b/inst/test_app/tests/testthat/_snaps/app-delete-code/requaltest-001_.png differ diff --git a/inst/test_app/tests/testthat/_snaps/app-delete-code/requaltest-002_.png b/inst/test_app/tests/testthat/_snaps/app-delete-code/requaltest-002_.png index aa4b4c38..bf37d81c 100644 Binary files a/inst/test_app/tests/testthat/_snaps/app-delete-code/requaltest-002_.png and b/inst/test_app/tests/testthat/_snaps/app-delete-code/requaltest-002_.png differ diff --git a/inst/test_app/tests/testthat/_snaps/app-edit-code/edit_code-001.json b/inst/test_app/tests/testthat/_snaps/app-edit-code/edit_code-001.json new file mode 100644 index 00000000..06cb876d --- /dev/null +++ b/inst/test_app/tests/testthat/_snaps/app-edit-code/edit_code-001.json @@ -0,0 +1,25 @@ +{ + "output": { + "codebook_ui_1-codes_ui": { + "html": "
\n
\n
\n

Code1<\/h3>\n
\n code<\/span>\n