mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-15 03:41:28 +00:00
feat: Persist response body scroll position across tabs (#3902)
This commit is contained in:
@@ -186,6 +186,8 @@ export default class CodeEditor extends React.Component {
|
||||
if (editor) {
|
||||
editor.setOption('lint', this.props.mode && editor.getValue().trim().length > 0 ? this.lintOptions : false);
|
||||
editor.on('change', this._onEdit);
|
||||
editor.on('scroll', this.onScroll);
|
||||
editor.scrollTo(null, this.props.initialScroll);
|
||||
this.addOverlay();
|
||||
|
||||
const getAllVariablesHandler = () => getAllVariables(this.props.collection, this.props.item);
|
||||
@@ -230,12 +232,18 @@ export default class CodeEditor extends React.Component {
|
||||
if (this.props.theme !== prevProps.theme && this.editor) {
|
||||
this.editor.setOption('theme', this.props.theme === 'dark' ? 'monokai' : 'default');
|
||||
}
|
||||
|
||||
if (this.props.initialScroll !== prevProps.initialScroll) {
|
||||
this.editor.scrollTo(null, this.props.initialScroll);
|
||||
}
|
||||
|
||||
this.ignoreChangeEvent = false;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.editor) {
|
||||
this.editor.off('change', this._onEdit);
|
||||
this.editor.off('scroll', this.onScroll);
|
||||
this.editor = null;
|
||||
}
|
||||
|
||||
@@ -271,6 +279,8 @@ export default class CodeEditor extends React.Component {
|
||||
this.editor.setOption('mode', 'brunovariables');
|
||||
};
|
||||
|
||||
onScroll = (event) => this.props.onScroll?.(event);
|
||||
|
||||
_onEdit = () => {
|
||||
if (!this.ignoreChangeEvent && this.editor) {
|
||||
this.editor.setOption('lint', this.editor.getValue().trim().length > 0 ? this.lintOptions : false);
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import CodeEditor from 'components/CodeEditor/index';
|
||||
import { get } from 'lodash';
|
||||
import find from 'lodash/find';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { updateResponsePaneScrollPosition } from 'providers/ReduxStore/slices/tabs';
|
||||
import { sendRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { Document, Page } from 'react-pdf';
|
||||
import 'pdfjs-dist/build/pdf.worker';
|
||||
@@ -51,6 +53,10 @@ const QueryResultPreview = ({
|
||||
displayedTheme
|
||||
}) => {
|
||||
const preferences = useSelector((state) => state.app.preferences);
|
||||
const tabs = useSelector((state) => state.tabs.tabs);
|
||||
const activeTabUid = useSelector((state) => state.tabs.activeTabUid);
|
||||
const focusedTab = find(tabs, (t) => t.uid === activeTabUid);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [numPages, setNumPages] = useState(null);
|
||||
@@ -66,9 +72,19 @@ const QueryResultPreview = ({
|
||||
if (disableRunEventListener) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(sendRequest(item, collection.uid));
|
||||
};
|
||||
|
||||
const onScroll = (event) => {
|
||||
dispatch(
|
||||
updateResponsePaneScrollPosition({
|
||||
uid: focusedTab.uid,
|
||||
scrollY: event.doc.scrollTop
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
switch (previewTab?.mode) {
|
||||
case 'preview-web': {
|
||||
const webViewSrc = data.replace('<head>', `<head><base href="${item.requestSent?.url || ''}">`);
|
||||
@@ -111,8 +127,10 @@ const QueryResultPreview = ({
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
theme={displayedTheme}
|
||||
onRun={onRun}
|
||||
onScroll={onScroll}
|
||||
value={formattedData}
|
||||
mode={mode}
|
||||
initialScroll={focusedTab.responsePaneScrollPosition || 0}
|
||||
readOnly
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -45,7 +45,7 @@ import {
|
||||
} from './index';
|
||||
|
||||
import { each } from 'lodash';
|
||||
import { closeAllCollectionTabs } from 'providers/ReduxStore/slices/tabs';
|
||||
import { closeAllCollectionTabs, updateResponsePaneScrollPosition } from 'providers/ReduxStore/slices/tabs';
|
||||
import { resolveRequestFilename } from 'utils/common/platform';
|
||||
import { parsePathParams, splitOnFirst } from 'utils/url/index';
|
||||
import { sendCollectionOauth2Request as _sendCollectionOauth2Request } from 'utils/network/index';
|
||||
@@ -258,6 +258,13 @@ export const sendRequest = (item, collectionUid) => (dispatch, getState) => {
|
||||
const requestUid = uuid();
|
||||
itemCopy.requestUid = requestUid;
|
||||
|
||||
await dispatch(
|
||||
updateResponsePaneScrollPosition({
|
||||
uid: state.tabs.activeTabUid,
|
||||
scrollY: 0
|
||||
})
|
||||
);
|
||||
|
||||
await dispatch(
|
||||
initRunRequestEvent({
|
||||
requestUid,
|
||||
@@ -1627,27 +1634,33 @@ export const clearOauth2Cache = (payload) => async (dispatch, getState) => {
|
||||
};
|
||||
|
||||
// todo: could be removed
|
||||
export const loadRequestViaWorker = ({ collectionUid, pathname }) => (dispatch, getState) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const { ipcRenderer } = window;
|
||||
ipcRenderer.invoke('renderer:load-request-via-worker', { collectionUid, pathname }).then(resolve).catch(reject);
|
||||
});
|
||||
};
|
||||
export const loadRequestViaWorker =
|
||||
({ collectionUid, pathname }) =>
|
||||
(dispatch, getState) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const { ipcRenderer } = window;
|
||||
ipcRenderer.invoke('renderer:load-request-via-worker', { collectionUid, pathname }).then(resolve).catch(reject);
|
||||
});
|
||||
};
|
||||
|
||||
// todo: could be removed
|
||||
export const loadRequest = ({ collectionUid, pathname }) => (dispatch, getState) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const { ipcRenderer } = window;
|
||||
ipcRenderer.invoke('renderer:load-request', { collectionUid, pathname }).then(resolve).catch(reject);
|
||||
});
|
||||
};
|
||||
export const loadRequest =
|
||||
({ collectionUid, pathname }) =>
|
||||
(dispatch, getState) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const { ipcRenderer } = window;
|
||||
ipcRenderer.invoke('renderer:load-request', { collectionUid, pathname }).then(resolve).catch(reject);
|
||||
});
|
||||
};
|
||||
|
||||
export const loadLargeRequest = ({ collectionUid, pathname }) => (dispatch, getState) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const { ipcRenderer } = window;
|
||||
ipcRenderer.invoke('renderer:load-large-request', { collectionUid, pathname }).then(resolve).catch(reject);
|
||||
});
|
||||
};
|
||||
export const loadLargeRequest =
|
||||
({ collectionUid, pathname }) =>
|
||||
(dispatch, getState) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const { ipcRenderer } = window;
|
||||
ipcRenderer.invoke('renderer:load-large-request', { collectionUid, pathname }).then(resolve).catch(reject);
|
||||
});
|
||||
};
|
||||
|
||||
export const mountCollection =
|
||||
({ collectionUid, collectionPathname, brunoConfig }) =>
|
||||
@@ -1671,16 +1684,17 @@ export const showInFolder = (collectionPath) => () => {
|
||||
});
|
||||
};
|
||||
|
||||
export const updateRunnerConfiguration = (collectionUid, selectedRequestItems, requestItemsOrder, delay) => (dispatch) => {
|
||||
dispatch(
|
||||
_updateRunnerConfiguration({
|
||||
collectionUid,
|
||||
selectedRequestItems,
|
||||
requestItemsOrder,
|
||||
delay
|
||||
})
|
||||
);
|
||||
};
|
||||
export const updateRunnerConfiguration =
|
||||
(collectionUid, selectedRequestItems, requestItemsOrder, delay) => (dispatch) => {
|
||||
dispatch(
|
||||
_updateRunnerConfiguration({
|
||||
collectionUid,
|
||||
selectedRequestItems,
|
||||
requestItemsOrder,
|
||||
delay
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
export const updateActiveConnectionsInStore = (activeConnectionIds) => (dispatch, getState) => {
|
||||
dispatch(updateActiveConnections(activeConnectionIds));
|
||||
|
||||
@@ -62,10 +62,10 @@ export const tabsSlice = createSlice({
|
||||
? preview
|
||||
: !nonReplaceableTabTypes.includes(type),
|
||||
...(uid ? { folderUid: uid } : {})
|
||||
}
|
||||
};
|
||||
|
||||
state.activeTabUid = uid;
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
state.tabs.push({
|
||||
@@ -74,6 +74,7 @@ export const tabsSlice = createSlice({
|
||||
requestPaneWidth: null,
|
||||
requestPaneTab: requestPaneTab || defaultRequestPaneTab,
|
||||
responsePaneTab: 'response',
|
||||
responsePaneScrollPosition: null,
|
||||
type: type || 'request',
|
||||
...(uid ? { folderUid: uid } : {}),
|
||||
preview: preview !== undefined
|
||||
@@ -126,6 +127,13 @@ export const tabsSlice = createSlice({
|
||||
tab.responsePaneTab = action.payload.responsePaneTab;
|
||||
}
|
||||
},
|
||||
updateResponsePaneScrollPosition: (state, action) => {
|
||||
const tab = find(state.tabs, (t) => t.uid === action.payload.uid);
|
||||
|
||||
if (tab) {
|
||||
tab.responsePaneScrollPosition = action.payload.scrollY;
|
||||
}
|
||||
},
|
||||
closeTabs: (state, action) => {
|
||||
const activeTab = find(state.tabs, (t) => t.uid === state.activeTabUid);
|
||||
const tabUids = action.payload.tabUids || [];
|
||||
@@ -167,8 +175,8 @@ export const tabsSlice = createSlice({
|
||||
const tab = find(state.tabs, (t) => t.uid === uid);
|
||||
if (tab) {
|
||||
tab.preview = false;
|
||||
} else{
|
||||
console.error("Tab not found!")
|
||||
} else {
|
||||
console.error('Tab not found!');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -181,6 +189,7 @@ export const {
|
||||
updateRequestPaneTabWidth,
|
||||
updateRequestPaneTab,
|
||||
updateResponsePaneTab,
|
||||
updateResponsePaneScrollPosition,
|
||||
closeTabs,
|
||||
closeAllCollectionTabs,
|
||||
makeTabPermanent
|
||||
|
||||
Reference in New Issue
Block a user