Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion R/app_ui.R
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ app_ui <- function(request) {
path = app_sys("app/www"),
app_title = "ESQapp"
),
tags$script(src = "www/app_exit_protection.js")
tags$script(src = "www/app_exit_protection.js"),
tags$script(src = "www/enhanced_file_input.js")
),
# Your application UI logic
bslib::page_navbar(
Expand Down
109 changes: 78 additions & 31 deletions R/mod_import.R
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,40 @@
mod_import_ui <- function(id) {
ns <- NS(id)
tagList(
shinyFiles::shinyFilesButton(
ns("projectConfigurationFile"),
"Select Project Configuration",
"Please select the projectConfiguration excel file",
multiple = FALSE
tags$div(
id = ns("dropzone"), # enhanced_file_input.js targets this ID
bslib::card(
height = "auto",
style = "border: 2px dashed #007bff; margin-bottom: 15px;",
bslib::card_body(
class = "text-center p-4",
fileInput(
ns("projectConfigurationFile"),
label = NULL,
accept = c(".xlsx", ".xls"),
buttonLabel = "Browse Files",
placeholder = "No file selected",
width = "100%"
),
div(
style = "margin-top: 15px;",
tags$div(style = "font-size: 2.5rem; color: #007bff; margin-bottom: 10px;", "📁"),
tags$h6("Project Configuration File",
style = "margin: 10px 0 5px 0; color: #495057; font-weight: 600;"),
tags$small("Select an Excel file (.xlsx, .xls)",
style = "color: #6c757d; display: block; margin-bottom: 5px;"),
tags$small("Drag and drop supported",
style = "color: #28a745; font-style: italic;")
)
)
)
),
uiOutput(ns("selected_file_path"))
uiOutput(ns("selected_file_path")),
# Visual state while dragging over the dropzone
tags$style(HTML(sprintf(
"#%s.dragover { border-color:#28a745 !important; background-color:#e8f5e8; }",
ns("dropzone")
)))
)
}

Expand All @@ -27,30 +54,25 @@ mod_import_server <- function(id, r, DROPDOWNS) {
moduleServer(id, function(input, output, session) {
ns <- session$ns

volumes <- c(
"Current Project" = getwd(),
# "Test Project" = testthat::test_path("data"),
Home = Sys.getenv("R_USER"),
shinyFiles::getVolumes()()
)

shinyFiles::shinyFileChoose(input,
id = "projectConfigurationFile",
roots = volumes,
filetypes = c("xlsx"),
session = session
)

projectConfiguration <- reactive({
req(input$projectConfigurationFile)
projectConfigurationFilePath <- shinyFiles::parseFilePaths(
volumes,
input$projectConfigurationFile
)

req(projectConfigurationFilePath$datapath)

esqlabsR::createDefaultProjectConfiguration(path = projectConfigurationFilePath$datapath)
# Get the uploaded file info
file_info <- input$projectConfigurationFile
req(file_info$datapath)

# Validate file extension
file_ext <- tools::file_ext(file_info$name)
if (!file_ext %in% c("xlsx", "xls")) {
showNotification(
"Please select an Excel file (.xlsx or .xls)",
type = "error",
duration = 5
)
return(NULL)
}

esqlabsR::createDefaultProjectConfiguration(path = file_info$datapath)
})

observeEvent(projectConfiguration(), {
Expand Down Expand Up @@ -107,14 +129,39 @@ mod_import_server <- function(id, r, DROPDOWNS) {
})

output$selected_file_path <- renderUI({
req(projectConfiguration())
req(input$projectConfigurationFile)

verbatimTextOutput(ns("selected_file_path_text"))
file_info <- input$projectConfigurationFile

# Create a nice display for the selected file
bslib::card(
style = "margin-top: 10px; border-left: 4px solid #28a745;",
bslib::card_body(
style = "padding: 10px 15px;",
div(
style = "display: flex; align-items: center;",
span(
style = "color: #28a745; margin-right: 8px; font-size: 1.2em;",
"✓"
),
div(
tags$strong("Selected File:", style = "color: #495057;"),
br(),
tags$small(file_info$name, style = "color: #6c757d;"),
br(),
tags$small(
paste("Size:", round(file_info$size / 1024, 1), "KB"),
style = "color: #6c757d;"
)
)
)
)
)
})

output$selected_file_path_text <- renderPrint({
req(projectConfiguration())
projectConfiguration()$projectConfigurationFilePath
req(input$projectConfigurationFile)
input$projectConfigurationFile$name
})

# Share project configuration path with the export module
Expand Down
89 changes: 89 additions & 0 deletions inst/app/www/enhanced_file_input.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Enhanced File Input with Drag and Drop
$(function() {
function initDropzones() {
$("[id$='-dropzone']").each(function() {
var dropzone = $(this);
if (dropzone.data("dropzone-init")) return; // already initialized
dropzone.data("dropzone-init", true);

var fileInput = dropzone.find("input[type='file']").first();
if (!fileInput.length) return;

// Prevent double opening after drop
var suppressNextClick = false;

// Highlight on drag
dropzone.on("dragover dragenter", function(e) {
e.preventDefault(); e.stopPropagation();
dropzone.addClass("dragover");
});

dropzone.on("dragleave dragend", function(e) {
e.preventDefault(); e.stopPropagation();
dropzone.removeClass("dragover");
});

// Drop handler
dropzone.on("drop", function(e) {
e.preventDefault(); e.stopPropagation();
dropzone.removeClass("dragover");

var dt = e.originalEvent && e.originalEvent.dataTransfer;
if (!dt || !dt.files || !dt.files.length) return;

var file = dt.files[0];
var ext = (file.name.split(".").pop() || "").toLowerCase();
if (["xlsx","xls"].indexOf(ext) === -1) {
showFeedback(dropzone, "Invalid file type. Please select .xlsx or .xls", "error");
return;
}

try {
var list = new DataTransfer();
list.items.add(file);
fileInput[0].files = list.files;
fileInput.trigger("change");
showFeedback(dropzone, "File selected: " + file.name + " • " + (file.size/1024).toFixed(1) + " KB", "success");
} catch (err) {
// Fallback
showFeedback(dropzone, "Drag-drop not supported; click to choose file.", "error");
// Only click the input if we really need to
fileInput.trigger("click");
}

// Prevent the "ghost click" that some browsers emit after drop
suppressNextClick = true;
setTimeout(function(){ suppressNextClick = false; }, 250);
});

// Only clicks on the dropzone *itself* (empty area) open the picker
dropzone.on("click", function(e) {
// If click originated from a child (button/label/input), do nothing
if (e.target !== e.currentTarget) return;
if (suppressNextClick) return;
fileInput.trigger("click");
});

// If user clicks the native input/button/label, stop bubbling to zone
fileInput.on("click", function(e) {
e.stopPropagation();
});

function showFeedback(zone, msg, type) {
zone.find(".upload-feedback").remove();
var el = $('<div class="upload-feedback" style="margin-top:10px;"></div>');
el.css("color", type === "success" ? "#28a745" : "#dc3545").text(msg);
zone.append(el);
if (type === "error") {
setTimeout(function(){ el.fadeOut(300, function(){ el.remove(); }); }, 3000);
}
}
});
}

// run once on page load
initDropzones();

// re-run when Shiny inserts/replaces UI
$(document).on("shiny:value", function(){ initDropzones(); });
});
84 changes: 84 additions & 0 deletions inst/app/www/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,87 @@
/* .tab-content>.active.html-fill-container {
width: calc(100% + 15%) !important;
} */

/* Enhanced File Input Styling */
.modern-file-input {
transition: all 0.3s ease;
cursor: pointer;
}

.modern-file-input:hover {
border-color: #007bff !important;
background-color: #f0f8ff !important;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,123,255,0.15);
}

/* Style the file input container */
.form-group.shiny-input-container {
margin-bottom: 0;
}

/* Style the file input button */
.btn-default {
background: linear-gradient(135deg, #007bff 0%, #0056b3 100%);
border: none;
color: white;
padding: 10px 20px;
border-radius: 6px;
font-weight: 500;
transition: all 0.3s ease;
}

.btn-default:hover {
background: linear-gradient(135deg, #0056b3 0%, #004085 100%);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0,123,255,0.3);
}

/* File input drag and drop enhancement */
.file-input-enhanced {
position: relative;
border: 2px dashed #dee2e6;
border-radius: 8px;
padding: 20px;
text-align: center;
background: #f8f9fa;
transition: all 0.3s ease;
}

.file-input-enhanced.dragover {
border-color: #28a745;
background-color: #e8f5e8;
}

/* Selected file display styling */
.selected-file-display {
border-left: 4px solid #28a745;
background: linear-gradient(135deg, #f8fff8 0%, #e8f5e8 100%);
border-radius: 0 6px 6px 0;
}

/* Card hover effects */
.card {
transition: all 0.3s ease;
}

.card:hover {
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}

/* File upload progress indicator */
.upload-progress {
width: 100%;
height: 4px;
background: #e9ecef;
border-radius: 2px;
overflow: hidden;
margin-top: 10px;
}

.upload-progress-bar {
height: 100%;
background: linear-gradient(90deg, #007bff, #28a745);
width: 0%;
transition: width 0.3s ease;
}
54 changes: 54 additions & 0 deletions tests/testthat/test-mod_import_improvements.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Test for the improved file selection dialog
# This file tests the new functionality in mod_import.R

test_that("mod_import_ui creates modern file input interface", {
# Test the UI function
ui_output <- mod_import_ui("test")

# Should contain a bslib::card
expect_true(any(grepl("card", as.character(ui_output))))

# Should contain fileInput instead of shinyFilesButton
expect_true(any(grepl("fileInput", as.character(ui_output))))

# Should not contain shinyFilesButton
expect_false(any(grepl("shinyFilesButton", as.character(ui_output))))

# Should contain drag and drop text
expect_true(any(grepl("Drag and drop", as.character(ui_output))))

# Should contain Excel file acceptance
expect_true(any(grepl("\\.xlsx", as.character(ui_output))))
})

test_that("file input accepts correct file types", {
# Test that the fileInput accepts .xlsx and .xls files
ui_output <- mod_import_ui("test")
ui_string <- as.character(ui_output)

# Should accept both xlsx and xls
expect_true(any(grepl("accept.*xlsx", ui_string)))
expect_true(any(grepl("accept.*xls", ui_string)))
})

test_that("modern styling is applied", {
# Test that modern styling classes and attributes are present
ui_output <- mod_import_ui("test")
ui_string <- as.character(ui_output)

# Should have blue dashed border styling
expect_true(any(grepl("border.*dashed.*007bff", ui_string)))

# Should have center text alignment
expect_true(any(grepl("text-center", ui_string)))

# Should contain file icon emoji
expect_true(any(grepl("📁", ui_string)))
})

# Note: Server function tests would require setting up a Shiny test environment
# These would test:
# - File validation logic
# - Error notifications for invalid files
# - Proper file info extraction
# - Integration with esqlabsR::createDefaultProjectConfiguration