@@ -975,6 +975,14 @@ def _custom_section():
975975 title = "Fetch all remaining pages" ,
976976 style = {"display" : "none" },
977977 ),
978+ html .Button (
979+ [html .I (className = "bi bi-x-lg me-1" ), "Abort" ],
980+ id = "load-all-abort-btn" ,
981+ n_clicks = 0 ,
982+ className = "load-all-abort-btn" ,
983+ title = "Cancel loading" ,
984+ style = {"display" : "none" },
985+ ),
978986 ], className = "response-panel" ),
979987 ], className = "main-content" ),
980988 ], className = "app-body" ),
@@ -1821,13 +1829,14 @@ def _load_all_worker(last_req, host, token, list_key, initial_data):
18211829 Output ("load-all-ticker" , "disabled" ),
18221830 Output ("fetch-status-bar" , "children" , allow_duplicate = True ),
18231831 Output ("sp-load-all-btn" , "style" , allow_duplicate = True ),
1832+ Output ("load-all-abort-btn" , "style" ),
18241833 Input ("sp-load-all-btn" , "n_clicks" ),
18251834 State ("last-request" , "data" ),
18261835 State ("conn-config" , "data" ),
18271836 prevent_initial_call = True ,
18281837)
18291838def start_load_all (n_clicks , last_req , conn_config ):
1830- NO = (True , no_update , no_update )
1839+ NO = (True , no_update , no_update , no_update )
18311840 if not n_clicks or not last_req :
18321841 return NO
18331842 initial_data = last_req .get ("initial_data" , {})
@@ -1858,7 +1867,7 @@ def start_load_all(n_clicks, last_req, conn_config):
18581867 "Loading page 1…" ,
18591868 ], className = "fetch-status-inner loading" )
18601869
1861- return False , status , {"display" : "none" }
1870+ return False , status , {"display" : "none" }, { "display" : "inline-flex" }
18621871
18631872
18641873# 11h-tick: Poll progress from background thread
@@ -1868,31 +1877,33 @@ def start_load_all(n_clicks, last_req, conn_config):
18681877 Output ("chips-store" , "data" , allow_duplicate = True ),
18691878 Output ("fetch-status-bar" , "children" , allow_duplicate = True ),
18701879 Output ("load-all-ticker" , "disabled" , allow_duplicate = True ),
1880+ Output ("load-all-abort-btn" , "style" , allow_duplicate = True ),
18711881 Input ("load-all-ticker" , "n_intervals" ),
18721882 State ("response-cache" , "data" ),
18731883 prevent_initial_call = True ,
18741884)
18751885def poll_load_all (n_intervals , cache ):
1876- NO = (no_update , no_update , no_update , no_update , no_update )
1886+ NO = (no_update , no_update , no_update , no_update , no_update , no_update )
18771887 state = _load_all_state
18781888 pages = state .get ("pages" , 0 )
18791889 total = state .get ("total_items" , 0 )
18801890 elapsed = state .get ("elapsed_ms" , 0 )
1891+ HIDE = {"display" : "none" }
18811892
18821893 if state .get ("running" ):
18831894 # Still loading — update status bar only
18841895 status = html .Div ([
18851896 html .I (className = "bi bi-arrow-repeat me-2 spin-icon" ),
18861897 f"Loading page { pages + 1 } … ({ total :,} items so far · { elapsed :,} ms)" ,
18871898 ], className = "fetch-status-inner loading" )
1888- return no_update , no_update , no_update , status , False
1899+ return no_update , no_update , no_update , status , False , no_update
18891900
18901901 # Auto-dismiss: if finished_at was set, wait 5s then clear status bar
18911902 finished_at = state .get ("finished_at" )
18921903 if finished_at and not state .get ("done" ):
18931904 if time .time () - finished_at >= 5 :
18941905 state .pop ("finished_at" , None )
1895- return no_update , no_update , no_update , "" , True # clear status, stop ticker
1906+ return no_update , no_update , no_update , "" , True , HIDE # clear status, stop ticker
18961907 return NO # keep ticking, waiting to dismiss
18971908
18981909 if not state .get ("done" ):
@@ -1902,8 +1913,8 @@ def poll_load_all(n_intervals, cache):
19021913 if state .get ("error" ):
19031914 status = html .Div ([
19041915 html .I (className = "bi bi-exclamation-triangle-fill me-2" ),
1905- f"Error after { pages } pages ({ total :,} items): { state [ 'error' ] } " ,
1906- ], className = "fetch-status-inner error " )
1916+ f"Aborted after { pages } pages ({ total :,} items · { elapsed :, } ms) " ,
1917+ ], className = "fetch-status-inner cancelled " )
19071918 else :
19081919 status = html .Div ([
19091920 html .I (className = "bi bi-check-circle-fill me-2" ),
@@ -1934,7 +1945,25 @@ def poll_load_all(n_intervals, cache):
19341945 # Mark done, set dismiss timer — keep ticker running for auto-dismiss
19351946 _load_all_state .update ({"done" : False , "items" : [], "finished_at" : time .time ()})
19361947
1937- return build_response_panel (merged_result , chips ), new_cache , chips or None , status , False
1948+ return build_response_panel (merged_result , chips ), new_cache , chips or None , status , False , HIDE
1949+
1950+
1951+ # 11h-abort: Stop background Load All thread
1952+ @app .callback (
1953+ Output ("fetch-status-bar" , "children" , allow_duplicate = True ),
1954+ Output ("load-all-abort-btn" , "style" , allow_duplicate = True ),
1955+ Input ("load-all-abort-btn" , "n_clicks" ),
1956+ prevent_initial_call = True ,
1957+ )
1958+ def abort_load_all (n_clicks ):
1959+ if not n_clicks :
1960+ return no_update , no_update
1961+ _load_all_state ["running" ] = False
1962+ _load_all_state ["error" ] = "Cancelled"
1963+ return html .Div ([
1964+ html .I (className = "bi bi-x-circle-fill me-2" ),
1965+ "Aborting…" ,
1966+ ], className = "fetch-status-inner cancelled" ), {"display" : "none" }
19381967
19391968
19401969# 13. Search filter
@@ -2190,7 +2219,8 @@ def update_curl_display(last_req, conn_config):
21902219)
21912220
21922221
2193- # Reparent the Load All button into the response-meta bar after each render
2222+ # Reparent the Load All button into the response-meta bar,
2223+ # and the Abort button into the fetch-status-bar, after each render
21942224app .clientside_callback (
21952225 """
21962226 function(children, style) {
@@ -2199,6 +2229,11 @@ def update_curl_display(last_req, conn_config):
21992229 if (anchor && btn) {
22002230 anchor.appendChild(btn);
22012231 }
2232+ var statusBar = document.getElementById('fetch-status-bar');
2233+ var abortBtn = document.getElementById('load-all-abort-btn');
2234+ if (statusBar && abortBtn) {
2235+ statusBar.appendChild(abortBtn);
2236+ }
22022237 return window.dash_clientside.no_update;
22032238 }
22042239 """ ,
0 commit comments