alexdum commited on
Commit
7d5ddfc
·
1 Parent(s): 24357b3

feat: Implement URL parameter parsing for app initialization and synchronize app state with the parent page URL.

Browse files
Files changed (2) hide show
  1. server.R +199 -1
  2. www/app.js +31 -0
server.R CHANGED
@@ -90,6 +90,123 @@ server <- function(input, output, session) {
90
  loading_diagnostics <- reactiveVal("")
91
  previous_station_choices <- reactiveVal(NULL) # Track previous choices to avoid blink
92
  previous_date_range <- reactiveVal(NULL) # Track previous date range for bi-directional sync
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
 
94
  # --- Reactive Data Sources (Resolution Dependent) ---
95
  observeEvent(input$data_resolution,
@@ -240,6 +357,59 @@ server <- function(input, output, session) {
240
  }
241
  })
242
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243
  # --- Selection Logic via Dropdown ---
244
  observeEvent(input$station_selector, {
245
  req(input$station_selector)
@@ -1011,7 +1181,9 @@ server <- function(input, output, session) {
1011
  fetch_stage(0)
1012
 
1013
  # Navigate to Dashboard tab
1014
- updateNavbarPage(session, "main_nav", selected = "Dashboard")
 
 
1015
 
1016
  # Show rendering message and then unfreeze after delay
1017
  session$sendCustomMessage("freezeUI", list(text = "Rendering plots..."))
@@ -1407,4 +1579,30 @@ server <- function(input, output, session) {
1407
  plot_list
1408
  )
1409
  })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1410
  }
 
90
  loading_diagnostics <- reactiveVal("")
91
  previous_station_choices <- reactiveVal(NULL) # Track previous choices to avoid blink
92
  previous_date_range <- reactiveVal(NULL) # Track previous date range for bi-directional sync
93
+ url_initialized <- reactiveVal(FALSE)
94
+
95
+ # --- URL Parameter Parsing ---
96
+ parse_url_params <- function(query) {
97
+ params <- list()
98
+ if (length(query) == 0) return(params)
99
+
100
+ # Helper to decode
101
+ decode <- function(x) URLdecode(x)
102
+
103
+ # Parse query string
104
+ pairs <- strsplit(query, "&")[[1]]
105
+ for (pair in pairs) {
106
+ parts <- strsplit(pair, "=")[[1]]
107
+ if (length(parts) == 2) {
108
+ key <- parts[1]
109
+ val <- decode(parts[2])
110
+ params[[key]] <- val
111
+ }
112
+ }
113
+ params
114
+ }
115
+
116
+ # Observer: Apply URL params on app startup
117
+ observe({
118
+ req(!url_initialized())
119
+ query <- session$clientData$url_search
120
+ # Wait for query to be available (it might be empty string initially)
121
+ # But we also want to handle no-params case to set initialized=TRUE
122
+
123
+ # Parse query (even if empty to confirm no params)
124
+ # Note: session$clientData$url_search usually starts with "?"
125
+ q_str <- sub("^\\?", "", query)
126
+ params <- parse_url_params(q_str)
127
+
128
+ # If params exist, apply them
129
+ if (length(params) > 0) {
130
+
131
+ # 1. Resolution
132
+ if (!is.null(params$resolution)) {
133
+ updateRadioButtons(session, "data_resolution", selected = params$resolution)
134
+ }
135
+
136
+ # 2. Date Range
137
+ if (!is.null(params$start) && !is.null(params$end)) {
138
+ updateDateRangeInput(session, "date_range", start = params$start, end = params$end)
139
+ }
140
+
141
+ # 3. Station Selection
142
+ if (!is.null(params$station)) {
143
+ station_ref <- params$station
144
+ # We need to wait for stations to load?
145
+ # The all_stations() reactive depends on resolution.
146
+ # Just updating the input might trigger the search.
147
+ # Note: station_selector choices are updated dynamically.
148
+ # We might need to handle this carefully if stations aren't loaded yet.
149
+ # However, updateSelectizeInput usually works nicely.
150
+
151
+ # Check if we need to set selected
152
+ shinyjs::delay(500, {
153
+ # Try to find ID if name was passed
154
+ # For now just set the value, assuming ID or Name match
155
+ # DWD station selector choices are "Name (ID)" = ID.
156
+
157
+ # If passed param is a name, we might need to lookup.
158
+ # But if we rely on broadcast sending Name, URL has Name.
159
+ # DWD selector needs ID.
160
+
161
+ # Search in current all_stations()
162
+ current_st <- isolate(all_stations())
163
+ if (!is.null(current_st)) {
164
+ # Try match ID
165
+ match <- current_st %>% filter(id == station_ref)
166
+ if (nrow(match) == 0) {
167
+ # Try match Name
168
+ match <- current_st %>% filter(name == station_ref)
169
+ }
170
+
171
+ if (nrow(match) > 0) {
172
+ target_id <- match$id[1]
173
+ updateSelectizeInput(session, "station_selector", selected = target_id)
174
+
175
+ # Also trigger details panel if view implies it
176
+ # The observer for station_selector will run and trigger map logic
177
+ }
178
+ }
179
+ })
180
+ }
181
+
182
+ # 4. View/Tab
183
+ if (!is.null(params$view)) {
184
+ view <- params$view
185
+ shinyjs::delay(800, {
186
+ if (view == "map") {
187
+ nav_select("main_nav", "Map View") # using bslib way or updateNavbarPage
188
+ # DWD ui uses page_navbar id="main_nav".
189
+ # updateNavbarPage(session, "main_nav", selected = "Map View") should work?
190
+ # Or custom message if updateNavbarPage isn't standard in bslib setups?
191
+ # UI line 3: id="main_nav".
192
+ } else if (view == "station-info") {
193
+ updateNavbarPage(session, "main_nav", selected = "Stations Info")
194
+ } else if (grepl("dashboard", view)) {
195
+ updateNavbarPage(session, "main_nav", selected = "Dashboard")
196
+
197
+ if (view == "dashboard-data") {
198
+ shinyjs::delay(200, {
199
+ nav_select("dashboard_subtabs", "Data")
200
+ # navset_card_pill id="dashboard_subtabs"
201
+ })
202
+ }
203
+ }
204
+ })
205
+ }
206
+ }
207
+
208
+ url_initialized(TRUE)
209
+ })
210
 
211
  # --- Reactive Data Sources (Resolution Dependent) ---
212
  observeEvent(input$data_resolution,
 
357
  }
358
  })
359
 
360
+
361
+ # Helper: Broadcast current state to parent page
362
+ broadcast_state <- function(view_override = NULL) {
363
+ # Get active station
364
+ sid <- current_station_id()
365
+ st_meta <- NULL
366
+ if (!is.null(sid)) {
367
+ all <- isolate(all_stations()) # Use isolate to avoid dependency loop if called inside observer?
368
+ # Actually active observers call this, so it's fine.
369
+ if (!is.null(all)) {
370
+ st_meta <- all %>% filter(id == sid) %>% head(1)
371
+ }
372
+ }
373
+
374
+ station_id <- if (!is.null(st_meta) && nrow(st_meta) > 0) as.character(st_meta$id) else NULL
375
+ station_name <- if (!is.null(st_meta) && nrow(st_meta) > 0) as.character(st_meta$name) else NULL
376
+ landname <- if (!is.null(st_meta) && nrow(st_meta) > 0) as.character(st_meta$state) else NULL
377
+
378
+ # Ensure we have a valid resolution string (Capitalized from UI?)
379
+ resolution <- input$data_resolution
380
+
381
+ # Determine current view
382
+ main_tab <- input$main_nav
383
+ view <- if (!is.null(view_override)) {
384
+ view_override
385
+ } else if (!is.null(main_tab)) {
386
+ if (main_tab == "Map View") "map"
387
+ else if (main_tab == "Stations Info") "station-info"
388
+ else if (main_tab == "Dashboard") {
389
+ subtab <- input$dashboard_subtabs
390
+ # If subtab is NULL/loading, default to plots
391
+ if (!is.null(subtab) && subtab == "Data") "dashboard-data"
392
+ else "dashboard-plots"
393
+ }
394
+ else "map"
395
+ } else {
396
+ "map"
397
+ }
398
+
399
+ start_date <- if (!is.null(input$date_range)) as.character(input$date_range[1]) else NULL
400
+ end_date <- if (!is.null(input$date_range)) as.character(input$date_range[2]) else NULL
401
+
402
+ session$sendCustomMessage("updateParentURL", list(
403
+ station = station_id,
404
+ stationName = station_name,
405
+ landname = landname,
406
+ resolution = resolution,
407
+ view = view,
408
+ start = start_date,
409
+ end = end_date
410
+ ))
411
+ }
412
+
413
  # --- Selection Logic via Dropdown ---
414
  observeEvent(input$station_selector, {
415
  req(input$station_selector)
 
1181
  fetch_stage(0)
1182
 
1183
  # Navigate to Dashboard tab
1184
+ if (input$main_nav != "Dashboard") {
1185
+ updateNavbarPage(session, "main_nav", selected = "Dashboard")
1186
+ }
1187
 
1188
  # Show rendering message and then unfreeze after delay
1189
  session$sendCustomMessage("freezeUI", list(text = "Rendering plots..."))
 
1579
  plot_list
1580
  )
1581
  })
1582
+ # --- URL Synchronization Observers ---
1583
+
1584
+ # 1. Tab changes
1585
+ observeEvent(input$main_nav, {
1586
+ broadcast_state()
1587
+ }, ignoreInit = TRUE)
1588
+
1589
+ # 2. Station ID change (covers map click and selector)
1590
+ observeEvent(current_station_id(), {
1591
+ broadcast_state()
1592
+ }, ignoreInit = TRUE)
1593
+
1594
+ # 3. Dashboard subtab changes
1595
+ observeEvent(input$dashboard_subtabs, {
1596
+ broadcast_state()
1597
+ }, ignoreInit = TRUE)
1598
+
1599
+ # 4. Resolution changes
1600
+ observeEvent(input$data_resolution, {
1601
+ broadcast_state()
1602
+ }, ignoreInit = TRUE)
1603
+
1604
+ # 5. Date Range changes
1605
+ observeEvent(input$date_range, {
1606
+ broadcast_state()
1607
+ }, ignoreInit = TRUE)
1608
  }
www/app.js CHANGED
@@ -38,3 +38,34 @@ Shiny.addCustomMessageHandler('unfreezeUI', function (message) {
38
  $('body').removeClass('ui-frozen');
39
  $('.frozen-overlay').removeClass('active');
40
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  $('body').removeClass('ui-frozen');
39
  $('.frozen-overlay').removeClass('active');
40
  });
41
+
42
+ // Switch Tab helper
43
+ Shiny.addCustomMessageHandler('switchTab', function (message) {
44
+ console.log("Switching to tab: " + message.tabId);
45
+ // Find the nav link
46
+ var tabLink = $('a[data-value="' + message.tabId + '"]');
47
+ if (tabLink.length > 0) {
48
+ // Trigger click natively to ensure Shiny inputs update
49
+ tabLink[0].click();
50
+ } else {
51
+ console.warn("Tab not found: " + message.tabId);
52
+ }
53
+ });
54
+
55
+ // Update parent page URL (for SEO/AdSense integration)
56
+ Shiny.addCustomMessageHandler('updateParentURL', function (message) {
57
+ console.log("Updating parent URL with state:", message);
58
+ // Send message to parent window (Quarto page)
59
+ if (window.parent !== window) {
60
+ window.parent.postMessage({
61
+ type: 'dwd-state-update', // Distinct type for DWD
62
+ station: message.station || null,
63
+ stationName: message.stationName || null,
64
+ landname: message.landname || null,
65
+ resolution: message.resolution || null,
66
+ view: message.view || null,
67
+ start: message.start || null,
68
+ end: message.end || null
69
+ }, '*');
70
+ }
71
+ });