Update app.R
Browse files
app.R
CHANGED
|
@@ -494,98 +494,173 @@ clean_college_data <- function(data, teams = NA) {
|
|
| 494 |
|
| 495 |
|
| 496 |
# ============================================================
|
| 497 |
-
# CSS
|
| 498 |
# ============================================================
|
| 499 |
-
|
| 500 |
-
|
| 501 |
-
|
| 502 |
-
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
|
| 506 |
-
|
| 507 |
-
|
| 508 |
-
|
| 509 |
-
|
| 510 |
-
|
| 511 |
-
|
| 512 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 513 |
|
| 514 |
# ============================================================
|
| 515 |
-
#
|
| 516 |
# ============================================================
|
| 517 |
-
|
| 518 |
-
|
| 519 |
-
|
| 520 |
-
|
| 521 |
-
|
| 522 |
-
|
| 523 |
-
|
| 524 |
-
|
| 525 |
-
|
| 526 |
-
|
| 527 |
-
|
| 528 |
-
|
| 529 |
-
|
| 530 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 531 |
)
|
| 532 |
)
|
| 533 |
|
| 534 |
-
|
| 535 |
-
|
| 536 |
-
|
| 537 |
-
|
| 538 |
-
|
| 539 |
-
|
| 540 |
-
|
| 541 |
-
|
| 542 |
-
|
| 543 |
-
|
| 544 |
-
|
| 545 |
-
|
| 546 |
-
|
| 547 |
-
|
| 548 |
-
|
| 549 |
-
|
| 550 |
-
|
| 551 |
-
|
| 552 |
-
|
| 553 |
-
|
| 554 |
-
|
| 555 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 556 |
),
|
| 557 |
-
|
| 558 |
-
|
| 559 |
-
|
| 560 |
-
|
| 561 |
-
|
| 562 |
-
|
| 563 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 564 |
)
|
| 565 |
)
|
| 566 |
-
)
|
|
|
|
|
|
|
| 567 |
)
|
| 568 |
|
| 569 |
-
ui <- fluidPage(uiOutput("page"))
|
| 570 |
-
|
| 571 |
# ============================================================
|
| 572 |
# Server
|
| 573 |
# ============================================================
|
| 574 |
server <- function(input, output, session) {
|
| 575 |
|
| 576 |
-
logged_in <- reactiveVal(FALSE)
|
| 577 |
-
|
| 578 |
-
output$page <- renderUI({ if (logged_in()) app_ui else login_ui })
|
| 579 |
-
|
| 580 |
-
observeEvent(input$login, {
|
| 581 |
-
if (nzchar(PASSWORD) && identical(input$login_password, PASSWORD)) {
|
| 582 |
-
logged_in(TRUE)
|
| 583 |
-
output$wrong_pass <- renderText("")
|
| 584 |
-
} else {
|
| 585 |
-
output$wrong_pass <- renderText("Incorrect password, please try again.")
|
| 586 |
-
}
|
| 587 |
-
})
|
| 588 |
-
|
| 589 |
scraped_data <- reactiveVal(NULL)
|
| 590 |
scrape_polling <- reactiveVal(FALSE)
|
| 591 |
scrape_status_msg <- reactiveVal("Ready.")
|
|
|
|
| 494 |
|
| 495 |
|
| 496 |
# ============================================================
|
| 497 |
+
# Theme + CSS (Hawks look, inlined — no theme.R needed)
|
| 498 |
# ============================================================
|
| 499 |
+
HAWKS_ORANGE <- "#F25B00"; HAWKS_BLUE <- "#164EA7"; HAWKS_DARK <- "#0D1B2A"
|
| 500 |
+
HAWKS_LIGHT <- "#F5F7FA"; HAWKS_GRAY <- "#6B7280"; HAWKS_BORDER <- "#E2E8F0"
|
| 501 |
+
|
| 502 |
+
hawks_theme <- bs_theme(
|
| 503 |
+
version = 5, bg = "#FFFFFF", fg = HAWKS_DARK,
|
| 504 |
+
primary = HAWKS_BLUE, secondary = HAWKS_ORANGE,
|
| 505 |
+
base_font = font_google("Inter"), heading_font = font_google("Inter"),
|
| 506 |
+
`border-radius` = "0.5rem", `font-size-base` = "0.925rem"
|
| 507 |
+
)
|
| 508 |
+
|
| 509 |
+
hawks_css <- tags$style(HTML(glue("
|
| 510 |
+
* {{ box-sizing:border-box; }}
|
| 511 |
+
body {{ background:{HAWKS_LIGHT}; font-family:'Inter',system-ui,sans-serif; margin:0; }}
|
| 512 |
+
.hawks-shell {{ display:flex; flex-direction:column; min-height:100vh; }}
|
| 513 |
+
.hawks-topbar {{ background:{HAWKS_DARK}; padding:0 24px; display:flex; align-items:center;
|
| 514 |
+
justify-content:space-between; border-bottom:3px solid {HAWKS_ORANGE}; }}
|
| 515 |
+
.topbar-brand {{ display:flex; align-items:center; gap:10px; padding:14px 0; }}
|
| 516 |
+
.topbar-brand .app-title {{ color:#fff; font-size:16px; font-weight:800; }}
|
| 517 |
+
.topbar-title {{ color:#fff; font-size:26px; font-weight:800; flex:1; text-align:center; white-space:nowrap; }}
|
| 518 |
+
.topbar-spacer {{ width:110px; }}
|
| 519 |
+
.hawks-content {{ flex:1; padding:24px; }}
|
| 520 |
+
.data-card {{ background:#fff; border:1px solid {HAWKS_BORDER}; border-radius:8px; overflow:hidden; }}
|
| 521 |
+
.data-card-hdr {{ padding:10px 14px; border-bottom:1px solid {HAWKS_BORDER};
|
| 522 |
+
display:flex; align-items:center; justify-content:space-between; }}
|
| 523 |
+
.data-card-title {{ font-size:11px; font-weight:700; color:{HAWKS_DARK};
|
| 524 |
+
text-transform:uppercase; letter-spacing:.05em; }}
|
| 525 |
+
.data-card-sub {{ font-size:10px; color:#9CA3AF; }}
|
| 526 |
+
.data-card-body {{ padding:16px; }}
|
| 527 |
+
.status-box {{ background:#eef3fb; border-left:4px solid {HAWKS_BLUE}; padding:14px;
|
| 528 |
+
border-radius:4px; white-space:pre-wrap; font-family:monospace; font-size:13px; color:{HAWKS_DARK}; }}
|
| 529 |
+
.btn-scrape {{ background:{HAWKS_BLUE} !important; border:none !important; color:#fff !important;
|
| 530 |
+
font-weight:700; width:100%; }}
|
| 531 |
+
.btn-scrape:hover {{ background:#0f3d82 !important; }}
|
| 532 |
+
.btn-hf {{ background:{HAWKS_ORANGE} !important; border:none !important; color:#fff !important;
|
| 533 |
+
font-weight:700; width:100%; }}
|
| 534 |
+
.btn-hf:hover {{ background:#d44f00 !important; }}
|
| 535 |
+
")))
|
| 536 |
|
| 537 |
# ============================================================
|
| 538 |
+
# Login overlay (JS, matches the other apps)
|
| 539 |
# ============================================================
|
| 540 |
+
login_css <- tags$style(HTML("
|
| 541 |
+
#login_screen { position:fixed; inset:0; z-index:9999; display:flex; flex-direction:column;
|
| 542 |
+
align-items:center; justify-content:center;
|
| 543 |
+
background:linear-gradient(135deg,#001a3a 0%,#003366 40%,#0a4a8a 100%);
|
| 544 |
+
transition:opacity .8s ease, transform .8s ease; }
|
| 545 |
+
#login_screen.fade-out { opacity:0; transform:scale(1.02); pointer-events:none; }
|
| 546 |
+
#login_screen .login-logo { width:120px; height:120px; margin-bottom:20px; border-radius:50%;
|
| 547 |
+
object-fit:cover; border:3px solid rgba(255,165,0,.6); box-shadow:0 0 30px rgba(255,165,0,.2); }
|
| 548 |
+
#login_screen .login-title { font-size:42px; font-weight:800; color:#fff; letter-spacing:1.5px;
|
| 549 |
+
margin-bottom:6px; text-align:center; }
|
| 550 |
+
#login_screen .login-subtitle { font-size:16px; color:rgba(255,165,0,.7); letter-spacing:3px;
|
| 551 |
+
text-transform:uppercase; margin-bottom:40px; }
|
| 552 |
+
#login_screen .login-accent { width:60px; height:3px; background:#FF8C00; margin:0 auto 30px; border-radius:2px; }
|
| 553 |
+
#login_screen .login-box { background:rgba(255,255,255,.06); border:1px solid rgba(255,165,0,.2);
|
| 554 |
+
border-radius:12px; padding:32px 40px; backdrop-filter:blur(10px); width:340px; text-align:center; }
|
| 555 |
+
#login_screen .login-box input[type='password'] { width:100%; padding:12px 16px;
|
| 556 |
+
border:1px solid rgba(255,165,0,.3); border-radius:6px; background:rgba(255,255,255,.08);
|
| 557 |
+
color:#fff; font-size:15px; margin-bottom:16px; outline:none; text-align:center; letter-spacing:2px; }
|
| 558 |
+
#login_screen .login-box input[type='password']:focus { border-color:#FF8C00; }
|
| 559 |
+
#login_screen .login-btn { width:100%; padding:12px; border:none; border-radius:6px;
|
| 560 |
+
background:#FF8C00; color:#fff; font-size:14px; font-weight:700; letter-spacing:1px;
|
| 561 |
+
text-transform:uppercase; cursor:pointer; }
|
| 562 |
+
#login_screen .login-btn:hover { background:#e67e00; }
|
| 563 |
+
#login_screen .login-error { color:#ff6b6b; font-size:13px; margin-top:12px; min-height:20px; }
|
| 564 |
+
"))
|
| 565 |
+
|
| 566 |
+
login_overlay <- tags$div(id = "login_screen",
|
| 567 |
+
tags$img(class = "login-logo", src = logo_uri),
|
| 568 |
+
tags$div(class = "login-title", "TrackMan Scraper"),
|
| 569 |
+
tags$div(class = "login-accent"),
|
| 570 |
+
tags$div(class = "login-subtitle", "Hyannis Harbor Hawks"),
|
| 571 |
+
tags$div(class = "login-box",
|
| 572 |
+
tags$input(id = "login_pw", type = "password", placeholder = "Enter password"),
|
| 573 |
+
tags$button(id = "login_btn", class = "login-btn", "Enter"),
|
| 574 |
+
tags$div(id = "login_error", class = "login-error")
|
| 575 |
)
|
| 576 |
)
|
| 577 |
|
| 578 |
+
login_js <- tags$script(HTML(paste0("
|
| 579 |
+
var APP_PASSWORD = '", PASSWORD, "';
|
| 580 |
+
function tryLogin() {
|
| 581 |
+
var pw = document.getElementById('login_pw').value;
|
| 582 |
+
if (pw === APP_PASSWORD) {
|
| 583 |
+
document.getElementById('login_screen').classList.add('fade-out');
|
| 584 |
+
setTimeout(function(){ document.getElementById('login_screen').style.display='none'; }, 800);
|
| 585 |
+
} else {
|
| 586 |
+
document.getElementById('login_error').textContent = 'Incorrect password';
|
| 587 |
+
document.getElementById('login_pw').value = '';
|
| 588 |
+
}
|
| 589 |
+
}
|
| 590 |
+
document.addEventListener('DOMContentLoaded', function(){
|
| 591 |
+
document.getElementById('login_btn').addEventListener('click', tryLogin);
|
| 592 |
+
document.getElementById('login_pw').addEventListener('keydown', function(e){
|
| 593 |
+
if (e.key === 'Enter') tryLogin();
|
| 594 |
+
});
|
| 595 |
+
});
|
| 596 |
+
")))
|
| 597 |
+
|
| 598 |
+
# ============================================================
|
| 599 |
+
# UI
|
| 600 |
+
# ============================================================
|
| 601 |
+
ui <- fluidPage(
|
| 602 |
+
theme = hawks_theme,
|
| 603 |
+
hawks_css,
|
| 604 |
+
login_css,
|
| 605 |
+
tags$div(class = "hawks-shell",
|
| 606 |
+
tags$div(class = "hawks-topbar",
|
| 607 |
+
tags$div(class = "topbar-brand",
|
| 608 |
+
tags$svg(width = "24", height = "24", viewBox = "0 0 30 30", fill = "none",
|
| 609 |
+
tags$polygon(points = "15,2 28,26 15,21 2,26", fill = HAWKS_ORANGE, opacity = "0.9"),
|
| 610 |
+
tags$polygon(points = "15,2 15,21 2,26", fill = "#fff", opacity = "0.18")),
|
| 611 |
+
tags$span(class = "app-title", "Scraper")
|
| 612 |
+
),
|
| 613 |
+
tags$div(class = "topbar-title", "TrackMan Scraper \u2014 2026 Cape"),
|
| 614 |
+
tags$div(class = "topbar-spacer")
|
| 615 |
),
|
| 616 |
+
tags$div(class = "hawks-content",
|
| 617 |
+
fluidRow(
|
| 618 |
+
column(4,
|
| 619 |
+
tags$div(class = "data-card",
|
| 620 |
+
tags$div(class = "data-card-hdr",
|
| 621 |
+
tags$span(class = "data-card-title", "Controls"),
|
| 622 |
+
tags$span(class = "data-card-sub", "Source & dates")),
|
| 623 |
+
tags$div(class = "data-card-body",
|
| 624 |
+
radioButtons("scrape_source", "Data Source",
|
| 625 |
+
choices = c("TrackMan PBP" = "pbp", "TrackMan Positional" = "pos"), selected = "pbp"),
|
| 626 |
+
dateInput("start_date", "Start Date:", value = Sys.Date() - 1),
|
| 627 |
+
dateInput("end_date", "End Date:", value = Sys.Date() - 1),
|
| 628 |
+
br(),
|
| 629 |
+
actionButton("scrape_btn", "Scrape Data", class = "btn-scrape"),
|
| 630 |
+
br(), br(),
|
| 631 |
+
downloadButton("download_scrape", "Download CSV", style = "width:100%;"),
|
| 632 |
+
br(), br(),
|
| 633 |
+
actionButton("upload_hf_btn", "Upload to HF Dataset", class = "btn-hf")
|
| 634 |
+
)
|
| 635 |
+
)
|
| 636 |
+
),
|
| 637 |
+
column(8,
|
| 638 |
+
tags$div(class = "data-card", style = "margin-bottom:16px;",
|
| 639 |
+
tags$div(class = "data-card-hdr",
|
| 640 |
+
tags$span(class = "data-card-title", "Progress"),
|
| 641 |
+
tags$span(class = "data-card-sub", "Live status")),
|
| 642 |
+
tags$div(class = "data-card-body",
|
| 643 |
+
tags$div(class = "status-box", textOutput("scrape_status")))
|
| 644 |
+
),
|
| 645 |
+
tags$div(class = "data-card",
|
| 646 |
+
tags$div(class = "data-card-hdr",
|
| 647 |
+
tags$span(class = "data-card-title", "Data Preview"),
|
| 648 |
+
tags$span(class = "data-card-sub", "Scraped rows")),
|
| 649 |
+
tags$div(class = "data-card-body", DT::dataTableOutput("scrape_preview"))
|
| 650 |
+
)
|
| 651 |
+
)
|
| 652 |
)
|
| 653 |
)
|
| 654 |
+
),
|
| 655 |
+
login_overlay,
|
| 656 |
+
login_js
|
| 657 |
)
|
| 658 |
|
|
|
|
|
|
|
| 659 |
# ============================================================
|
| 660 |
# Server
|
| 661 |
# ============================================================
|
| 662 |
server <- function(input, output, session) {
|
| 663 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 664 |
scraped_data <- reactiveVal(NULL)
|
| 665 |
scrape_polling <- reactiveVal(FALSE)
|
| 666 |
scrape_status_msg <- reactiveVal("Ready.")
|