Skip to content

Commit 7ced94c

Browse files
committed
Add fetching indication
1 parent 3e75093 commit 7ced94c

1 file changed

Lines changed: 180 additions & 3 deletions

File tree

src/session.ts

Lines changed: 180 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -755,13 +755,160 @@ export async function getTableHtml(webview: Webview, file: string | undefined, t
755755
[class*="vscode"] .text-right {
756756
text-align: right;
757757
}
758+
759+
#gridContainer {
760+
position: relative;
761+
height: 100%;
762+
}
763+
764+
#fetchStatus {
765+
position: absolute;
766+
top: var(--fetch-status-top, 52px);
767+
right: 8px;
768+
z-index: 20;
769+
display: none;
770+
align-items: center;
771+
gap: 8px;
772+
padding: 6px 10px;
773+
border-radius: 4px;
774+
border: 1px solid var(--vscode-panel-border);
775+
background-color: var(--vscode-editorWidget-background);
776+
color: var(--vscode-editorWidget-foreground);
777+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
778+
font-size: 12px;
779+
max-width: min(68vw, 560px);
780+
}
781+
782+
#fetchStatus.visible {
783+
display: flex;
784+
}
785+
786+
#fetchStatus[data-state="warning"] {
787+
border-color: var(--vscode-inputValidation-warningBorder);
788+
}
789+
790+
#fetchStatus[data-state="error"] {
791+
border-color: var(--vscode-inputValidation-errorBorder);
792+
}
793+
794+
#fetchStatusText {
795+
word-break: break-word;
796+
}
797+
798+
#fetchRetryBtn {
799+
display: none;
800+
border: 0;
801+
padding: 3px 8px;
802+
background-color: var(--vscode-button-background);
803+
color: var(--vscode-button-foreground);
804+
cursor: pointer;
805+
white-space: nowrap;
806+
}
807+
808+
#fetchStatus.show-retry #fetchRetryBtn {
809+
display: inline-block;
810+
}
758811
</style>
759812
<script src="${String(webview.asWebviewUri(Uri.file(path.join(resDir, 'ag-grid-community.min.noStyle.js'))))}"></script>
760813
<script>
761814
const vscode = typeof acquireVsCodeApi === 'function' ? acquireVsCodeApi() : { postMessage: () => {} };
762815
let requestIdSeq = 1;
763816
const pending = new Map();
764817
let gridApi;
818+
let activeFetches = 0;
819+
let longFetchTimer;
820+
const LONG_FETCH_DELAY_MS = 2000;
821+
822+
function clearLongFetchTimer() {
823+
if (longFetchTimer) {
824+
clearTimeout(longFetchTimer);
825+
longFetchTimer = undefined;
826+
}
827+
}
828+
829+
function setFetchStatus(state, message, showRetry) {
830+
const statusEl = document.querySelector('#fetchStatus');
831+
const textEl = document.querySelector('#fetchStatusText');
832+
if (!statusEl || !textEl) {
833+
return;
834+
}
835+
836+
if (state === 'hidden') {
837+
statusEl.classList.remove('visible', 'show-retry');
838+
statusEl.dataset.state = '';
839+
textEl.textContent = '';
840+
return;
841+
}
842+
843+
statusEl.dataset.state = state;
844+
textEl.textContent = message;
845+
statusEl.classList.add('visible');
846+
statusEl.classList.toggle('show-retry', Boolean(showRetry));
847+
}
848+
849+
function updateFetchStatusPosition() {
850+
const containerEl = document.querySelector('#gridContainer');
851+
if (!containerEl) {
852+
return;
853+
}
854+
855+
let headerHeight = 0;
856+
if (gridApi && typeof gridApi.getSizesForCurrentTheme === 'function') {
857+
const sizes = gridApi.getSizesForCurrentTheme();
858+
if (sizes && Number.isFinite(sizes.headerHeight)) {
859+
headerHeight = Number(sizes.headerHeight);
860+
}
861+
}
862+
863+
if (!headerHeight) {
864+
const headerEl = document.querySelector('#myGrid .ag-header');
865+
if (headerEl) {
866+
headerHeight = headerEl.getBoundingClientRect().height;
867+
}
868+
}
869+
870+
const topOffset = Math.max(8, Math.round(headerHeight) + 8);
871+
containerEl.style.setProperty('--fetch-status-top', String(topOffset) + 'px');
872+
}
873+
874+
function beginFetch(message) {
875+
activeFetches += 1;
876+
if (activeFetches === 1) {
877+
setFetchStatus('loading', message || 'Fetching data...', false);
878+
clearLongFetchTimer();
879+
longFetchTimer = setTimeout(() => {
880+
if (activeFetches > 0) {
881+
setFetchStatus('warning', 'Still waiting for R session response. It may be busy running code.', false);
882+
}
883+
}, LONG_FETCH_DELAY_MS);
884+
}
885+
}
886+
887+
function finishFetch(ok, errorMessage) {
888+
activeFetches = Math.max(0, activeFetches - 1);
889+
if (activeFetches !== 0) {
890+
return;
891+
}
892+
893+
clearLongFetchTimer();
894+
if (ok) {
895+
setFetchStatus('hidden', '', false);
896+
} else {
897+
setFetchStatus('error', errorMessage || 'Failed to fetch data from R session.', true);
898+
}
899+
}
900+
901+
function retryCurrentPage() {
902+
if (!gridApi) {
903+
return;
904+
}
905+
setFetchStatus('loading', 'Retrying data fetch...', false);
906+
if (typeof gridApi.refreshInfiniteCache === 'function') {
907+
gridApi.refreshInfiniteCache();
908+
} else if (typeof gridApi.purgeInfiniteCache === 'function') {
909+
gridApi.purgeInfiniteCache();
910+
}
911+
}
765912
766913
function request(action, payload) {
767914
const requestId = requestIdSeq++;
@@ -815,13 +962,24 @@ export async function getTableHtml(webview: Webview, file: string | undefined, t
815962
if (gridApi) {
816963
gridApi.setGridOption('theme', getAgTheme());
817964
}
965+
updateFetchStatusPosition();
818966
}
819967
820968
async function initialize() {
821969
console.log('[dataview] agGrid object:', window.agGrid);
822970
console.log('[dataview] agGrid.Grid:', typeof window.agGrid.Grid);
823-
824-
const init = await request('init', {});
971+
972+
beginFetch('Loading data viewer metadata...');
973+
let init;
974+
try {
975+
init = await request('init', {});
976+
finishFetch(true);
977+
} catch (e) {
978+
const initError = e instanceof Error ? e.message : String(e);
979+
finishFetch(false, 'Failed to initialize data viewer: ' + initError);
980+
throw e;
981+
}
982+
825983
const columns = Array.isArray(init.columns) ? init.columns : [];
826984
827985
columns.forEach((column) => {
@@ -858,6 +1016,7 @@ export async function getTableHtml(webview: Webview, file: string | undefined, t
8581016
8591017
const datasource = {
8601018
getRows: async function(params) {
1019+
beginFetch('Fetching rows from R session...');
8611020
try {
8621021
const result = await request('page', {
8631022
startRow: params.startRow,
@@ -867,9 +1026,12 @@ export async function getTableHtml(webview: Webview, file: string | undefined, t
8671026
});
8681027
const resolvedLastRow = Number.isFinite(result.totalRows) ? result.totalRows : result.lastRow;
8691028
params.successCallback(result.rows || [], resolvedLastRow);
1029+
finishFetch(true);
8701030
} catch (e) {
8711031
console.error('[dataview] Failed to load page', e);
8721032
params.failCallback();
1033+
const pageError = e instanceof Error ? e.message : String(e);
1034+
finishFetch(false, 'Failed to fetch page: ' + pageError);
8731035
}
8741036
}
8751037
};
@@ -900,6 +1062,7 @@ export async function getTableHtml(webview: Webview, file: string | undefined, t
9001062
if (gridApi) {
9011063
gridApi.columnApi?.autoSizeAllColumns(false);
9021064
}
1065+
updateFetchStatusPosition();
9031066
}
9041067
};
9051068
@@ -908,6 +1071,7 @@ export async function getTableHtml(webview: Webview, file: string | undefined, t
9081071
console.log('[dataview] Creating grid with options:', gridOptions);
9091072
gridApi = window.agGrid.createGrid(gridDiv, gridOptions);
9101073
console.log('[dataview] Grid created successfully');
1074+
updateFetchStatusPosition();
9111075
} catch (e) {
9121076
console.error('[dataview] Grid creation failed:', e);
9131077
console.error('[dataview] Error stack:', e instanceof Error ? e.stack : 'N/A');
@@ -916,9 +1080,16 @@ export async function getTableHtml(webview: Webview, file: string | undefined, t
9161080
}
9171081
9181082
document.addEventListener('DOMContentLoaded', () => {
1083+
const retryBtn = document.querySelector('#fetchRetryBtn');
1084+
if (retryBtn) {
1085+
retryBtn.addEventListener('click', retryCurrentPage);
1086+
}
1087+
9191088
updateTheme();
9201089
initialize().catch((e) => {
9211090
console.error('[dataview] Initialization failed', e);
1091+
const initError = e instanceof Error ? e.message : String(e);
1092+
setFetchStatus('error', 'Initialization failed: ' + initError, true);
9221093
});
9231094
9241095
const observer = new MutationObserver(function () {
@@ -934,7 +1105,13 @@ export async function getTableHtml(webview: Webview, file: string | undefined, t
9341105
</script>
9351106
</head>
9361107
<body>
937-
<div id="myGrid" style="height: 100%;"></div>
1108+
<div id="gridContainer">
1109+
<div id="myGrid" style="height: 100%;"></div>
1110+
<div id="fetchStatus" data-state="" role="status" aria-live="polite">
1111+
<span id="fetchStatusText"></span>
1112+
<button id="fetchRetryBtn" type="button">Retry</button>
1113+
</div>
1114+
</div>
9381115
</body>
9391116
</html>
9401117
`;

0 commit comments

Comments
 (0)