diff --git a/packages/bruno-app/src/components/CodeEditor/index.js b/packages/bruno-app/src/components/CodeEditor/index.js index a83535300..4a8f0591a 100644 --- a/packages/bruno-app/src/components/CodeEditor/index.js +++ b/packages/bruno-app/src/components/CodeEditor/index.js @@ -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); diff --git a/packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js b/packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js index d4fbee07e..595c170e2 100644 --- a/packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js +++ b/packages/bruno-app/src/components/ResponsePane/QueryResult/QueryResultPreview/index.js @@ -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('', ``); @@ -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 /> ); diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js index 75b5ecc55..0c490df80 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -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)); diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/tabs.js b/packages/bruno-app/src/providers/ReduxStore/slices/tabs.js index 0225417ec..694239da6 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/tabs.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/tabs.js @@ -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