-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathui.R
More file actions
265 lines (232 loc) · 11.3 KB
/
ui.R
File metadata and controls
265 lines (232 loc) · 11.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
ui <- shiny::fluidPage( #Really, the whole UI is a navbarPage, but I wrap it in a fluidPage so I can stuff all the annoying "housekeeping elements here at the top, since navbarPage doesn't like receiving them.
style = "margin-top: -45px;",
tags$html(lang = "en"), #SET THE PAGE LANGUAGE TO ENGLISH FOR SCREEN READERS.
## Insert a custom disconnect message so that folks know what the failure state looks like and can take appropriate actions
disconnectMessage(text = " ", #I DUNNO THAT THIS DOES ANYTHING IF WE'RE GONNA REPLACE IT WITH A JS SCRIPT ANYWAY.
refresh = "Refresh the page",
width = "full",
top = "center",
size = 22,
background = "#7a0019",
colour = "white",
refreshColour = "#ffb71e",
css = "z-index: 100000 !important;"),
# Housekeeping items and head element stuff -------------------------------
#We use a nice waiter to show while the App preloads, with a custom spinner, text, and logo, plus styling and a fadeout.
waiter::useWaiter(),
waiter::waiter_preloader(html = shiny::tagList(
waiter::spin_loaders(6), shiny::br(), shiny::br(),
shiny::HTML("Charting your course..."), #Provides more text so that it doesn't need to go into the alt text.
shiny::br(), shiny::br(),
shiny::img(src = "maisrc-white.png", alt = "")
), #THIS ALT CAN BE EMPTY--THE TEXT ABOVE CONVEYS ALL NECCESSARY INFO.
color = "#7a0019",
fadeout = 500
),
#Here, we load in our CSS stylesheet.
tags$head(tags$link(
rel = "stylesheet",
type = "text/css",
href = "PI_app.css"
),
#LOAD IN GOOGLE ANALYTICS SCRIPT
shiny::tags$head(
shiny::tags$script(
src = "https://www.googletagmanager.com/gtag/js?id=G-DZN3JZEM2V",
async = ""
),
shiny::tags$script(
src = "js/googleanalytics.js"
)
),
#SCRIPT FOR TAB PILL COLORATION ON SUBMISSIONS TAB
tags$head(
tags$script(HTML("
$(document).on('shiny:inputchanged', function(event) {
//ON TAB CHANGE
if (event.name === 'submission_tabset') {
// RESET TAB PILL STYLES
$('#submission_tabset>li').removeClass('active-previous');
// FIND ACTIVE TAB, COLOR ALL TABS UP TO + INCLUDING IT
var foundActive = false;
$('#submission_tabset>li').each(function() {
if ($(this).hasClass('active')) {
foundActive = true;
$(this).addClass('active-previous');
} else if (!foundActive) {
$(this).addClass('active-previous');
}
});
}
});"))
),
#SCRIPT BLOCKING SCIENTIFIC NOTATION AND NEGATIVES FROM BEING ENTER FOR THESE TWO INPUTS
tags$script(
'
$(document).on("keydown", "#start_row2", function (e) {
if (e.key === "e" || e.key === "E" || e.key === "-") {
e.preventDefault();
return false;
}
});
$(document).on("keydown", "#end_row2", function (e) {
if (e.key === "e" || e.key === "E" || e.key === "-") {
e.preventDefault();
return false;
}
});
'
),
#JS FUNCTION THAT FORCES THE DATE PICKER POPUP ON THE SUBMISSIONS TAB THAT COMES ALONG WITH SURVEY START DATE SELECTOR TO BE A BIT ABOVE THE SELECTOR SO IT DOESN'T COVER UP THE TYPING BOX.
tags$script(HTML("
$(document).ready(function(){
// Function to adjust the datepicker position
function adjustDatepickerPosition(input) {
setTimeout(function(){
var datepicker = $('body').find('.datepicker');
var offset = $(input).offset();
var dpHeight = datepicker.outerHeight();
datepicker.css({
top: (offset.top - dpHeight - 10) + 'px',
left: offset.left + 'px'
});
}, 10);
}
// Bind focus and click events to the date input
$('#survey_date2').on('focus click', function(){
adjustDatepickerPosition(this);
});
// Also adjust position when the datepicker is shown
$(document).on('shown.bs.datepicker', function(e){
adjustDatepickerPosition($('#survey_date2'));
});
});
")),
#JS FUNCTION THAT CAN RECEIVE A MESSAGE FROM SHINY SAYING, HEY, THE SESSION JUST ENDED. WHEN THAT HAPPENS, IT'LL GIVE THE SHINY DISCONNECT OVERLAY THE ARIA ROLE OF ALERTDIALOG, MAKE IT FOCUSABLE, AND MOVE FOCUS THERE. ALSO, ENSURE THE DISCONNECT MESSAGE REFRESH LINK ACTUALLY HAS TEXT IN IT. LASTLY, THIS IS ALSO WHERE NEW DISCONNECT MESSAGE TEXT SHOULD GO.
tags$script(HTML("
$(document).on('shiny:disconnected', function(event) {
var el = document.getElementById('ss-connect-dialog');
if (el) {
// Create an accessible alert
var msgContainer = document.createElement('div');
msgContainer.innerHTML = 'P.I. Charter has timed out or crashed.<br>If you suspect a crash, contact <address><a href=\"mailto:bajcz003@umn.edu\" id = \"disconnectemail\">Dr. Alex Bajcz</a></address>.';
msgContainer.setAttribute('role', 'alertdialog');
msgContainer.setAttribute('tabindex', '-1');
msgContainer.setAttribute('aria-label', 'Error message');
msgContainer.style.outline = 'none';
msgContainer.style.marginBottom = '1em';
// Insert it at the beginning of the disconnect dialog
el.insertBefore(msgContainer, el.firstChild);
// WIPE OUT CONTENT THAT GETS ASSIGNED TO THIS LINK
const link = document.querySelector('#ss-connect-dialog > div > address > a');
if (link) {
const style = document.createElement('style');
style.textContent = `
#ss-connect-dialog address a::before {
content: none !important;
}
#ss-connect-dialog a#disconnectemail {
display: inline !important;
margin-top: 0px !important;
}
`;
document.head.appendChild(style);
}
// Shift focus
msgContainer.focus();
// MAKE SURE THAT THE DISCONNECT LINK ALSO HAS TEXT IN IT:
$(document).ready(function() {
var reloadLink = document.getElementById('ss-reload-link');
if (reloadLink) {
reloadLink.innerText = 'Refresh the page';
}
});
}
});
"))
),
shinyjs::useShinyjs(), #ENABLE SHINYJS
#THIS IS A SHINY LIVE REGION THAT IS NOT VISIBLE TO ANYONE BUT IS SCREEN-READER-ENABLED. WHENEVER WE GENERATE A NOTIFICATION, WE CAN TRIGGER A MESSAGE TO GO INTO THIS REGION, WHERE IT'LL THEN BE READ ALOUD TO SCREEN-READER USERS.
tags$div(id = "a11y-live-region",
`aria-live` = "assertive",
`aria-atomic` = "true",
class = "sr-only",
style = "position: absolute; width: 1px; height: 1px; margin: -1px; border: 0; padding: 0; overflow: hidden; clip: rect(0 0 0 0);"),
#HERE IS THE JAVASCRIPT CODE THAT WILL ALLOW THAT TO HAPPEN. IT'S TRIGGERED BY A SHINY CUSTOM MESSAGE, AND THEN INSERTS THE TEXT MESSAGE ARGUMENT INTO THE LIVE REGION.
tags$script(HTML("
Shiny.addCustomMessageHandler('a11yLiveMessage', function(message) {
var liveEl = document.getElementById('a11y-live-region');
if (liveEl) {
liveEl.textContent = message;
}
});
")),
# Core elements of the navbarPage -----------------------------------------
shiny::navbarPage(
collapsible = T, #Yes, make it collapsible except I revoke this feature in the CSS--it seems cleaner to do it that way.
id = "theNavBar",
lang = "en",
title = "",
header =
tags$header( #MORE SEMANTICALLY APPROPRIATE DIV
class = "flexthis flexcol width100 justifycenter flexwrap",
h1(tagList(
img(src = "maisrc-black.png",
alt = "MAISRC logo.",#THIS ALT SHOULD JUST BE THE TEXT SIGHTED PERSONS WOULD SEE HERE.
style = "min-width: 320px;"),
img(src = "PIlogocrop.png",
alt = "P.I. Charter.",#THIS ALT SHOULD JUST BE THE TEXT SIGHTED PERSONS WOULD SEE HERE.
style = "min-width: 320px;")
),
class = "flexthis flexrow width100 justifycenter flexwrap"), #BY ADDING AN H1 HERE, SCREEN READERS WILL BE ABLE TO RECOGNIZE THIS AS A TOP-LEVEL HEADER.
fluidRow(
tags$nav( #SEMANTICALLY APPROPRIATE DIV
class = "flexthis flexwrap justifycenter",
style = "margin: 10px auto !important; align-items: center;",
actionButton(
"switch_browse",
h2(shiny::span(shiny::HTML('<span class="fa-solid fa-binoculars" aria-hidden="true"></span>'), #SPAN TAGS SHOULD BE USED FOR ICONS INSTEAD OF <i>
"Overview"
), class = "navbuttons"), #BY WRAPPING IN H2s, SCREEN READERS WILL HOPEFULLY BE ABLE TO RECOGNIZE THESE AS 2ND-LEVEL HEADERS.
class = "navbuttons navbuttons_active" #STARTS WITH ACTIVE CLASS
),
actionButton(
"switch_leaderboard",
h2(shiny::span(shiny::HTML('<span class="fa-solid fa-medal" aria-hidden="true"></span>'),
"Leaderboard"
), class = "navbuttons"),
class = "navbuttons"
),
div(id = "nav_last_two",
actionButton(
"switch_submissions",
h2(shiny::span(shiny::HTML('<span class="fa-solid fa-file-import" aria-hidden="true"></span>'),
"Submissions"
), class = "navbuttons"),
class = "navbuttons"
),
actionButton(
"switch_records",
h2(shiny::span(shiny::HTML('<span class="fa-solid fa-clipboard" aria-hidden="true"></span>'),
"Records"
), class = "navbuttons"),
class = "navbuttons"
)
)
)
)
),
windowTitle = "P.I. Charter", #Our browser window blurb.
#Insert a footer with links, contact info, funding credits, and current version number.
footer = tags$footer(
shiny::HTML("<br>Created by the Minnesota Aquatic Invasive Species Research Center, University of Minnesota by <address><a href='mailto:bajcz003@umn.edu'>Dr. Alex Bajcz (bajcz003@umn.edu)</a></address>, under the current direction of <address><a href='mailto:walsh229@umn.edu'>Dr. Jake Walsh (walsh229@umn.edu)</a></address>.<br>
App last updated February 12 2026. Version 2.6.4. View the <a href = 'https://z.umn.edu/PI-AS' target = '_blank'>accessibility statement</a> (opens in new tab).<br>
Funding for this work was provided by the Minnesota Environment and Natural Resources Trust Fund as recommended by the Minnesota Aquatic Invasive Species Research Center and the Legislative-Citizen Commission on Minnesota Resources, and the State of Minnesota.<br><br>"),
id = "footerText",
style = "min-width: 320px;"),
# The tabs
browseUI(),
leaderboardUI(),
submissionsUI_V2(),
recordsUI(),
)) #End UI