Merge branch 'main' into feature/proxy-global-and-collection

# Conflicts:
#	packages/bruno-app/src/components/Preferences/General/index.js
#	packages/bruno-app/src/components/Preferences/index.js
#	packages/bruno-app/src/providers/App/useIpcEvents.js
#	packages/bruno-app/src/providers/Preferences/index.js
#	packages/bruno-electron/src/index.js
#	packages/bruno-electron/src/ipc/collection.js
#	packages/bruno-electron/src/store/preferences.js
This commit is contained in:
Mirko Golze 2023-10-15 17:06:39 +02:00
commit f2bdf2eaf6
26 changed files with 316 additions and 301 deletions

View File

@ -4,6 +4,7 @@ const StyledWrapper = styled.div`
div.CodeMirror { div.CodeMirror {
background: ${(props) => props.theme.codemirror.bg}; background: ${(props) => props.theme.codemirror.bg};
border: solid 1px ${(props) => props.theme.codemirror.border}; border: solid 1px ${(props) => props.theme.codemirror.border};
font-family: ${(props) => (props.font ? props.font : 'default')};
} }
.CodeMirror-overlayscroll-horizontal div, .CodeMirror-overlayscroll-horizontal div,

View File

@ -121,6 +121,7 @@ export default class CodeEditor extends React.Component {
<StyledWrapper <StyledWrapper
className="h-full w-full" className="h-full w-full"
aria-label="Code Editor" aria-label="Code Editor"
font={this.props.font}
ref={(node) => { ref={(node) => {
this._node = node; this._node = node;
}} }}

View File

@ -0,0 +1,7 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
color: ${(props) => props.theme.text};
`;
export default StyledWrapper;

View File

@ -0,0 +1,55 @@
import React, { useState } from 'react';
import get from 'lodash/get';
import { useSelector, useDispatch } from 'react-redux';
import { savePreferences } from 'providers/ReduxStore/slices/app';
import StyledWrapper from './StyledWrapper';
const Font = ({ close }) => {
const dispatch = useDispatch();
const preferences = useSelector((state) => state.app.preferences);
const [codeFont, setCodeFont] = useState(get(preferences, 'font.codeFont', 'default'));
const handleInputChange = (event) => {
setCodeFont(event.target.value);
};
const handleSave = () => {
dispatch(
savePreferences({
...preferences,
font: {
codeFont
}
})
).then(() => {
close();
});
};
return (
<StyledWrapper>
<label className="block font-medium">Code Editor Font</label>
<div className="input-container">
<input
type="text"
className="block textbox mt-2 w-full"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={handleInputChange}
defaultValue={codeFont}
/>
</div>
<div className="mt-10">
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>
Save
</button>
</div>
</StyledWrapper>
);
};
export default Font;

View File

@ -1,37 +1,46 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { usePreferences } from 'providers/Preferences'; import { useSelector, useDispatch } from 'react-redux';
import { savePreferences } from 'providers/ReduxStore/slices/app';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
import toast from 'react-hot-toast';
const General = () => { const General = ({ close }) => {
const { preferences, setPreferences } = usePreferences(); const preferences = useSelector((state) => state.app.preferences);
const dispatch = useDispatch();
const [tlsVerification, setTlsVerification] = useState(preferences.request.tlsVerification); const [sslVerification, setSslVerification] = useState(preferences.request.sslVerification);
const handleCheckboxChange = () => { const handleSave = () => {
const updatedPreferences = { dispatch(
...preferences, savePreferences({
request: { ...preferences,
...preferences.request, request: {
tlsVerification: !tlsVerification sslVerification
} }
};
setPreferences(updatedPreferences)
.then(() => {
setTlsVerification(!tlsVerification);
toast.success('Request settings saved successful.');
}) })
.catch((err) => { ).then(() => {
console.error(err); close();
}); });
}; };
return ( return (
<StyledWrapper> <StyledWrapper>
<div className="flex items-center mt-2"> <div className="flex items-center mt-2">
<input type="checkbox" checked={tlsVerification} onChange={handleCheckboxChange} className="mr-3 mousetrap" /> <input
TLS Certificate Verification id="ssl-verification"
type="checkbox"
checked={sslVerification}
onChange={() => setSslVerification(!sslVerification)}
className="mr-3 mousetrap"
/>
<label htmlFor="ssl-verification" className="select-none">
TLS Certificate Verification
</label>
</div>
<div className="mt-10">
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>
Save
</button>
</div> </div>
</StyledWrapper> </StyledWrapper>
); );

View File

@ -3,6 +3,7 @@ import classnames from 'classnames';
import React, { useState } from 'react'; import React, { useState } from 'react';
import Support from './Support'; import Support from './Support';
import General from './General'; import General from './General';
import Font from './Font';
import Theme from './Theme'; import Theme from './Theme';
import Proxy from './ProxySettings'; import Proxy from './ProxySettings';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
@ -19,20 +20,24 @@ const Preferences = ({ onClose }) => {
const getTabPanel = (tab) => { const getTabPanel = (tab) => {
switch (tab) { switch (tab) {
case 'general': { case 'general': {
return <General />; return <General close={onClose} />;
} }
case 'proxy': { case 'proxy': {
return <Proxy />; return <Proxy close={onClose} />;
} }
case 'theme': { case 'theme': {
return <Theme />; return <Theme close={onClose} />;
} }
case 'support': { case 'support': {
return <Support />; return <Support />;
} }
case 'font': {
return <Font close={onClose} />;
}
} }
}; };
@ -46,6 +51,9 @@ const Preferences = ({ onClose }) => {
<div className={getTabClassname('theme')} role="tab" onClick={() => setTab('theme')}> <div className={getTabClassname('theme')} role="tab" onClick={() => setTab('theme')}>
Theme Theme
</div> </div>
<div className={getTabClassname('font')} role="tab" onClick={() => setTab('font')}>
Font
</div>
<div className={getTabClassname('proxy')} role="tab" onClick={() => setTab('proxy')}> <div className={getTabClassname('proxy')} role="tab" onClick={() => setTab('proxy')}>
Proxy Proxy
</div> </div>

View File

@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import { useDispatch } from 'react-redux'; import get from 'lodash/get';
import { useDispatch, useSelector } from 'react-redux';
import CodeEditor from 'components/CodeEditor'; import CodeEditor from 'components/CodeEditor';
import { updateRequestGraphqlVariables } from 'providers/ReduxStore/slices/collections'; import { updateRequestGraphqlVariables } from 'providers/ReduxStore/slices/collections';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions'; import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
@ -10,6 +11,7 @@ const GraphQLVariables = ({ variables, item, collection }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const { storedTheme } = useTheme(); const { storedTheme } = useTheme();
const preferences = useSelector((state) => state.app.preferences);
const onEdit = (value) => { const onEdit = (value) => {
dispatch( dispatch(
@ -30,6 +32,7 @@ const GraphQLVariables = ({ variables, item, collection }) => {
collection={collection} collection={collection}
value={variables || ''} value={variables || ''}
theme={storedTheme} theme={storedTheme}
font={get(preferences, 'font.codeFont', 'default')}
onEdit={onEdit} onEdit={onEdit}
mode="javascript" mode="javascript"
onRun={onRun} onRun={onRun}

View File

@ -3,7 +3,7 @@ import get from 'lodash/get';
import CodeEditor from 'components/CodeEditor'; import CodeEditor from 'components/CodeEditor';
import FormUrlEncodedParams from 'components/RequestPane/FormUrlEncodedParams'; import FormUrlEncodedParams from 'components/RequestPane/FormUrlEncodedParams';
import MultipartFormParams from 'components/RequestPane/MultipartFormParams'; import MultipartFormParams from 'components/RequestPane/MultipartFormParams';
import { useDispatch } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { useTheme } from 'providers/Theme'; import { useTheme } from 'providers/Theme';
import { updateRequestBody } from 'providers/ReduxStore/slices/collections'; import { updateRequestBody } from 'providers/ReduxStore/slices/collections';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions'; import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
@ -14,6 +14,7 @@ const RequestBody = ({ item, collection }) => {
const body = item.draft ? get(item, 'draft.request.body') : get(item, 'request.body'); const body = item.draft ? get(item, 'draft.request.body') : get(item, 'request.body');
const bodyMode = item.draft ? get(item, 'draft.request.body.mode') : get(item, 'request.body.mode'); const bodyMode = item.draft ? get(item, 'draft.request.body.mode') : get(item, 'request.body.mode');
const { storedTheme } = useTheme(); const { storedTheme } = useTheme();
const preferences = useSelector((state) => state.app.preferences);
const onEdit = (value) => { const onEdit = (value) => {
dispatch( dispatch(
@ -48,6 +49,7 @@ const RequestBody = ({ item, collection }) => {
<CodeEditor <CodeEditor
collection={collection} collection={collection}
theme={storedTheme} theme={storedTheme}
font={get(preferences, 'font.codeFont', 'default')}
value={bodyContent[bodyMode] || ''} value={bodyContent[bodyMode] || ''}
onEdit={onEdit} onEdit={onEdit}
onRun={onRun} onRun={onRun}

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import get from 'lodash/get'; import get from 'lodash/get';
import { useDispatch } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import CodeEditor from 'components/CodeEditor'; import CodeEditor from 'components/CodeEditor';
import { updateRequestScript, updateResponseScript } from 'providers/ReduxStore/slices/collections'; import { updateRequestScript, updateResponseScript } from 'providers/ReduxStore/slices/collections';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions'; import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
@ -13,6 +13,7 @@ const Script = ({ item, collection }) => {
const responseScript = item.draft ? get(item, 'draft.request.script.res') : get(item, 'request.script.res'); const responseScript = item.draft ? get(item, 'draft.request.script.res') : get(item, 'request.script.res');
const { storedTheme } = useTheme(); const { storedTheme } = useTheme();
const preferences = useSelector((state) => state.app.preferences);
const onRequestScriptEdit = (value) => { const onRequestScriptEdit = (value) => {
dispatch( dispatch(
@ -45,6 +46,7 @@ const Script = ({ item, collection }) => {
collection={collection} collection={collection}
value={requestScript || ''} value={requestScript || ''}
theme={storedTheme} theme={storedTheme}
font={get(preferences, 'font.codeFont', 'default')}
onEdit={onRequestScriptEdit} onEdit={onRequestScriptEdit}
mode="javascript" mode="javascript"
onRun={onRun} onRun={onRun}
@ -57,6 +59,7 @@ const Script = ({ item, collection }) => {
collection={collection} collection={collection}
value={responseScript || ''} value={responseScript || ''}
theme={storedTheme} theme={storedTheme}
font={get(preferences, 'font.codeFont', 'default')}
onEdit={onResponseScriptEdit} onEdit={onResponseScriptEdit}
mode="javascript" mode="javascript"
onRun={onRun} onRun={onRun}

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import get from 'lodash/get'; import get from 'lodash/get';
import { useDispatch } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import CodeEditor from 'components/CodeEditor'; import CodeEditor from 'components/CodeEditor';
import { updateRequestTests } from 'providers/ReduxStore/slices/collections'; import { updateRequestTests } from 'providers/ReduxStore/slices/collections';
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions'; import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
@ -12,6 +12,7 @@ const Tests = ({ item, collection }) => {
const tests = item.draft ? get(item, 'draft.request.tests') : get(item, 'request.tests'); const tests = item.draft ? get(item, 'draft.request.tests') : get(item, 'request.tests');
const { storedTheme } = useTheme(); const { storedTheme } = useTheme();
const preferences = useSelector((state) => state.app.preferences);
const onEdit = (value) => { const onEdit = (value) => {
dispatch( dispatch(
@ -32,6 +33,7 @@ const Tests = ({ item, collection }) => {
collection={collection} collection={collection}
value={tests || ''} value={tests || ''}
theme={storedTheme} theme={storedTheme}
font={get(preferences, 'font.codeFont', 'default')}
onEdit={onEdit} onEdit={onEdit}
mode="javascript" mode="javascript"
onRun={onRun} onRun={onRun}

View File

@ -1,7 +1,8 @@
import React from 'react'; import React from 'react';
import get from 'lodash/get';
import CodeEditor from 'components/CodeEditor'; import CodeEditor from 'components/CodeEditor';
import { useTheme } from 'providers/Theme'; import { useTheme } from 'providers/Theme';
import { useDispatch } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { sendRequest } from 'providers/ReduxStore/slices/collections/actions'; import { sendRequest } from 'providers/ReduxStore/slices/collections/actions';
import classnames from 'classnames'; import classnames from 'classnames';
import { getContentType, safeStringifyJSON, safeParseXML } from 'utils/common'; import { getContentType, safeStringifyJSON, safeParseXML } from 'utils/common';
@ -13,6 +14,7 @@ import { useMemo } from 'react';
const QueryResult = ({ item, collection, data, width, disableRunEventListener, headers, error }) => { const QueryResult = ({ item, collection, data, width, disableRunEventListener, headers, error }) => {
const { storedTheme } = useTheme(); const { storedTheme } = useTheme();
const preferences = useSelector((state) => state.app.preferences);
const [tab, setTab] = useState('preview'); const [tab, setTab] = useState('preview');
const dispatch = useDispatch(); const dispatch = useDispatch();
const contentType = getContentType(headers); const contentType = getContentType(headers);
@ -111,7 +113,17 @@ const QueryResult = ({ item, collection, data, width, disableRunEventListener, h
return <img src={item.requestSent.url} alt="image" />; return <img src={item.requestSent.url} alt="image" />;
} }
return <CodeEditor collection={collection} theme={storedTheme} onRun={onRun} value={value} mode={mode} readOnly />; return (
<CodeEditor
collection={collection}
font={get(preferences, 'font.codeFont', 'default')}
theme={storedTheme}
onRun={onRun}
value={value}
mode={mode}
readOnly
/>
);
}, [tab, collection, storedTheme, onRun, value, mode]); }, [tab, collection, storedTheme, onRun, value, mode]);
return ( return (

View File

@ -1,10 +1,13 @@
import CodeEditor from 'components/CodeEditor/index'; import CodeEditor from 'components/CodeEditor/index';
import get from 'lodash/get';
import { HTTPSnippet } from 'httpsnippet'; import { HTTPSnippet } from 'httpsnippet';
import { useTheme } from 'providers/Theme/index'; import { useTheme } from 'providers/Theme/index';
import { buildHarRequest } from 'utils/codegenerator/har'; import { buildHarRequest } from 'utils/codegenerator/har';
import { useSelector } from 'react-redux';
const CodeView = ({ language, item }) => { const CodeView = ({ language, item }) => {
const { storedTheme } = useTheme(); const { storedTheme } = useTheme();
const preferences = useSelector((state) => state.app.preferences);
const { target, client, language: lang } = language; const { target, client, language: lang } = language;
let snippet = ''; let snippet = '';
@ -15,7 +18,15 @@ const CodeView = ({ language, item }) => {
snippet = 'Error generating code snippet'; snippet = 'Error generating code snippet';
} }
return <CodeEditor readOnly value={snippet} theme={storedTheme} mode={lang} />; return (
<CodeEditor
readOnly
value={snippet}
font={get(preferences, 'font.codeFont', 'default')}
theme={storedTheme}
mode={lang}
/>
);
}; };
export default CodeView; export default CodeView;

View File

@ -9,7 +9,6 @@ import StyledWrapper from './StyledWrapper';
import 'codemirror/theme/material.css'; import 'codemirror/theme/material.css';
import 'codemirror/theme/monokai.css'; import 'codemirror/theme/monokai.css';
import 'codemirror/addon/scroll/simplescrollbars.css'; import 'codemirror/addon/scroll/simplescrollbars.css';
import Documentation from 'components/Documentation';
const SERVER_RENDERED = typeof navigator === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true; const SERVER_RENDERED = typeof navigator === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
if (!SERVER_RENDERED) { if (!SERVER_RENDERED) {

View File

@ -3,7 +3,6 @@ import { Provider } from 'react-redux';
import { AppProvider } from 'providers/App'; import { AppProvider } from 'providers/App';
import { ToastProvider } from 'providers/Toaster'; import { ToastProvider } from 'providers/Toaster';
import { HotkeysProvider } from 'providers/Hotkeys'; import { HotkeysProvider } from 'providers/Hotkeys';
import { PreferencesProvider } from 'providers/Preferences';
import ReduxStore from 'providers/ReduxStore'; import ReduxStore from 'providers/ReduxStore';
import ThemeProvider from 'providers/Theme/index'; import ThemeProvider from 'providers/Theme/index';
@ -50,11 +49,9 @@ function MyApp({ Component, pageProps }) {
<ThemeProvider> <ThemeProvider>
<ToastProvider> <ToastProvider>
<AppProvider> <AppProvider>
<PreferencesProvider> <HotkeysProvider>
<HotkeysProvider> <Component {...pageProps} />
<Component {...pageProps} /> </HotkeysProvider>
</HotkeysProvider>
</PreferencesProvider>
</AppProvider> </AppProvider>
</ToastProvider> </ToastProvider>
</ThemeProvider> </ThemeProvider>

View File

@ -1,5 +1,5 @@
import Head from 'next/head'; import Head from 'next/head';
import IndexPage from 'pageComponents/Index'; import Bruno from './Bruno';
import GlobalStyle from '../globalStyles'; import GlobalStyle from '../globalStyles';
export default function Home() { export default function Home() {
@ -13,7 +13,7 @@ export default function Home() {
<GlobalStyle /> <GlobalStyle />
<main> <main>
<IndexPage /> <Bruno />
</main> </main>
</div> </div>
); );

View File

@ -1,6 +1,6 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import useTelemetry from './useTelemetry'; import useTelemetry from './useTelemetry';
import useCollectionTreeSync from './useCollectionTreeSync'; import useIpcEvents from './useIpcEvents';
import useCollectionNextAction from './useCollectionNextAction'; import useCollectionNextAction from './useCollectionNextAction';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { refreshScreenWidth } from 'providers/ReduxStore/slices/app'; import { refreshScreenWidth } from 'providers/ReduxStore/slices/app';
@ -10,7 +10,7 @@ export const AppContext = React.createContext();
export const AppProvider = (props) => { export const AppProvider = (props) => {
useTelemetry(); useTelemetry();
useCollectionTreeSync(); useIpcEvents();
useCollectionNextAction(); useCollectionNextAction();
const dispatch = useDispatch(); const dispatch = useDispatch();

View File

@ -14,11 +14,12 @@ import {
runFolderEvent, runFolderEvent,
brunoConfigUpdateEvent brunoConfigUpdateEvent
} from 'providers/ReduxStore/slices/collections'; } from 'providers/ReduxStore/slices/collections';
import { updatePreferences } from 'providers/ReduxStore/slices/app';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import { openCollectionEvent, collectionAddEnvFileEvent } from 'providers/ReduxStore/slices/collections/actions'; import { openCollectionEvent, collectionAddEnvFileEvent } from 'providers/ReduxStore/slices/collections/actions';
import { isElectron } from 'utils/common/platform'; import { isElectron } from 'utils/common/platform';
const useCollectionTreeSync = () => { const useIpcEvents = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
useEffect(() => { useEffect(() => {
@ -28,10 +29,6 @@ const useCollectionTreeSync = () => {
const { ipcRenderer } = window; const { ipcRenderer } = window;
const _openCollection = (pathname, uid, brunoConfig) => {
dispatch(openCollectionEvent(uid, pathname, brunoConfig));
};
const _collectionTreeUpdated = (type, val) => { const _collectionTreeUpdated = (type, val) => {
if (window.__IS_DEV__) { if (window.__IS_DEV__) {
console.log(type); console.log(type);
@ -82,70 +79,73 @@ const useCollectionTreeSync = () => {
} }
}; };
const _collectionAlreadyOpened = () => { ipcRenderer.invoke('renderer:ready');
toast.success('Collection is already opened'); const removeCollectionTreeUpdateListener = ipcRenderer.on('main:collection-tree-updated', _collectionTreeUpdated);
};
const _displayError = (error) => { const removeOpenCollectionListener = ipcRenderer.on('main:collection-opened', (pathname, uid, brunoConfig) => {
dispatch(openCollectionEvent(uid, pathname, brunoConfig));
});
const removeCollectionAlreadyOpenedListener = ipcRenderer.on('main:collection-already-opened', (pathname) => {
toast.success('Collection is already opened');
});
const removeDisplayErrorListener = ipcRenderer.on('main:display-error', (error) => {
if (typeof error === 'string') { if (typeof error === 'string') {
return toast.error(error || 'Something went wrong!'); return toast.error(error || 'Something went wrong!');
} }
if (typeof message === 'object') { if (typeof message === 'object') {
return toast.error(error.message || 'Something went wrong!'); return toast.error(error.message || 'Something went wrong!');
} }
}; });
const _scriptEnvironmentUpdate = (val) => { const removeScriptEnvUpdateListener = ipcRenderer.on('main:script-environment-update', (val) => {
dispatch(scriptEnvironmentUpdateEvent(val)); dispatch(scriptEnvironmentUpdateEvent(val));
}; });
const _processEnvUpdate = (val) => { const removeCollectionRenamedListener = ipcRenderer.on('main:collection-renamed', (val) => {
dispatch(processEnvUpdateEvent(val));
};
const _collectionRenamed = (val) => {
dispatch(collectionRenamedEvent(val)); dispatch(collectionRenamedEvent(val));
}; });
const _runFolderEvent = (val) => { const removeRunFolderEventListener = ipcRenderer.on('main:run-folder-event', (val) => {
dispatch(runFolderEvent(val)); dispatch(runFolderEvent(val));
}; });
const _runRequestEvent = (val) => { const removeRunRequestEventListener = ipcRenderer.on('main:run-request-event', (val) => {
dispatch(runRequestEvent(val)); dispatch(runRequestEvent(val));
}; });
ipcRenderer.invoke('renderer:ready-application'); const removeProcessEnvUpdatesListener = ipcRenderer.on('main:process-env-update', (val) => {
ipcRenderer.invoke('renderer:ready-collection'); dispatch(processEnvUpdateEvent(val));
});
const removeListener1 = ipcRenderer.on('main:collection-opened', _openCollection); const removeConsoleLogListener = ipcRenderer.on('main:console-log', (val) => {
const removeListener2 = ipcRenderer.on('main:collection-tree-updated', _collectionTreeUpdated);
const removeListener3 = ipcRenderer.on('main:collection-already-opened', _collectionAlreadyOpened);
const removeListener4 = ipcRenderer.on('main:display-error', _displayError);
const removeListener5 = ipcRenderer.on('main:script-environment-update', _scriptEnvironmentUpdate);
const removeListener6 = ipcRenderer.on('main:collection-renamed', _collectionRenamed);
const removeListener7 = ipcRenderer.on('main:run-folder-event', _runFolderEvent);
const removeListener8 = ipcRenderer.on('main:run-request-event', _runRequestEvent);
const removeListener9 = ipcRenderer.on('main:process-env-update', _processEnvUpdate);
const removeListener10 = ipcRenderer.on('main:console-log', (val) => {
console[val.type](...val.args); console[val.type](...val.args);
}); });
const removeListener11 = ipcRenderer.on('main:bruno-config-update', (val) => dispatch(brunoConfigUpdateEvent(val)));
const removeConfigUpdatesListener = ipcRenderer.on('main:bruno-config-update', (val) =>
dispatch(brunoConfigUpdateEvent(val))
);
const removePreferencesUpdatesListener = ipcRenderer.on('main:load-preferences', (val) => {
dispatch(updatePreferences(val));
});
return () => { return () => {
removeListener1(); removeCollectionTreeUpdateListener();
removeListener2(); removeOpenCollectionListener();
removeListener3(); removeCollectionAlreadyOpenedListener();
removeListener4(); removeDisplayErrorListener();
removeListener5(); removeScriptEnvUpdateListener();
removeListener6(); removeCollectionRenamedListener();
removeListener7(); removeRunFolderEventListener();
removeListener8(); removeRunRequestEventListener();
removeListener9(); removeProcessEnvUpdatesListener();
removeListener10(); removeConsoleLogListener();
removeListener11(); removeConfigUpdatesListener();
removePreferencesUpdatesListener();
}; };
}, [isElectron]); }, [isElectron]);
}; };
export default useCollectionTreeSync; export default useIpcEvents;

View File

@ -1,150 +0,0 @@
/**
* Preferences Provider
*
* This provider is responsible for managing the user's preferences in the app.
* The preferences are stored in the browser local storage.
*
* On start, an IPC event is published to the main process to set the preferences in the electron process.
*/
import { useEffect, createContext, useContext, useMemo } from 'react';
import * as Yup from 'yup';
import useLocalStorage from 'hooks/useLocalStorage/index';
import toast from 'react-hot-toast';
const requestSchema = Yup.object({
sslVerification: Yup.boolean(),
caCert: Yup.string().max(1024)
});
const proxySchema = Yup.object({
enabled: Yup.boolean(),
protocol: Yup.string().oneOf(['http', 'https', 'socks5']),
hostname: Yup.string()
.when('enabled', {
is: true,
then: (hostname) => hostname.required('Specify the hostname for your proxy.'),
otherwise: (hostname) => hostname.nullable()
})
.max(1024),
port: Yup.number()
.when('enabled', {
is: true,
then: (port) => port.typeError('Specify port between 1 and 65535'),
otherwise: (port) => port.nullable().transform((_, val) => (val ? Number(val) : null))
})
.min(1)
.max(65535),
auth: Yup.object()
.when('enabled', {
is: true,
then: Yup.object({
enabled: Yup.boolean(),
username: Yup.string()
.when(['enabled'], {
is: true,
then: (username) => username.required('Specify username for proxy authentication.')
})
.max(1024),
password: Yup.string()
.when('enabled', {
is: true,
then: (password) => password.required('Specify password for proxy authentication.')
})
.max(1024)
})
})
.optional(),
noProxy: Yup.string().optional().max(1024)
});
const preferencesSchema = Yup.object({
request: requestSchema,
proxy: proxySchema
});
export const PreferencesContext = createContext();
export const PreferencesProvider = (props) => {
// TODO: Remove migration later
const [localStorePreferences] = useLocalStorage('bruno.preferences');
const preferences = {};
const { ipcRenderer } = window;
useEffect(() => {
// TODO: Remove migration later
if (localStorePreferences?.request) {
console.log('migrate prefs from localStorage ' + JSON.stringify(localStorePreferences));
ipcRenderer
.invoke('renderer:migrate-preferences', localStorePreferences.request.sslVerification)
.then(() => {
localStorage.removeItem('bruno.preferences');
})
.catch((err) => {
toast.error(err.message || 'Preferences sync error');
});
}
const removeListener = ipcRenderer.on('main:preferences-read', (currentPreferences) => {
if (currentPreferences.request) {
preferences.request = currentPreferences.request;
}
if (currentPreferences.proxy) {
preferences.proxy = currentPreferences.proxy;
}
});
return () => {
removeListener();
};
}, [preferences, toast]);
const validatedSetPreferences = (newPreferences) => {
return new Promise((resolve, reject) => {
preferencesSchema
.validate(newPreferences, { abortEarly: true })
.then((validatedPreferences) => {
ipcRenderer
.invoke('renderer:set-preferences', validatedPreferences)
.then(() => {
preferences.request = validatedPreferences.request;
preferences.proxy = validatedPreferences.proxy;
})
.catch((err) => {
toast.error(err.message || 'Preferences sync error');
});
resolve(validatedPreferences);
})
.catch((error) => {
let errMsg = error.message || 'Preferences validation error';
toast.error(errMsg);
reject(error);
});
});
};
const value = useMemo(
() => ({
preferences,
setPreferences: validatedSetPreferences
}),
[preferences, validatedSetPreferences]
);
return (
<PreferencesContext.Provider value={value}>
<>{props.children}</>
</PreferencesContext.Provider>
);
};
export const usePreferences = () => {
const context = useContext(PreferencesContext);
if (context === undefined) {
throw new Error(`usePreferences must be used within a PreferencesProvider`);
}
return context;
};
export default PreferencesProvider;

View File

@ -1,11 +1,20 @@
import { createSlice } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit';
import toast from 'react-hot-toast';
const initialState = { const initialState = {
isDragging: false, isDragging: false,
idbConnectionReady: false, idbConnectionReady: false,
leftSidebarWidth: 222, leftSidebarWidth: 222,
screenWidth: 500, screenWidth: 500,
showHomePage: false showHomePage: false,
preferences: {
request: {
sslVerification: true
},
font: {
codeFont: 'default'
}
}
}; };
export const appSlice = createSlice({ export const appSlice = createSlice({
@ -29,6 +38,9 @@ export const appSlice = createSlice({
}, },
hideHomePage: (state) => { hideHomePage: (state) => {
state.showHomePage = false; state.showHomePage = false;
},
updatePreferences: (state, action) => {
state.preferences = action.payload;
} }
} }
}); });
@ -39,7 +51,25 @@ export const {
updateLeftSidebarWidth, updateLeftSidebarWidth,
updateIsDragging, updateIsDragging,
showHomePage, showHomePage,
hideHomePage hideHomePage,
updatePreferences
} = appSlice.actions; } = appSlice.actions;
export const savePreferences = (preferences) => (dispatch, getState) => {
return new Promise((resolve, reject) => {
const { ipcRenderer } = window;
ipcRenderer
.invoke('renderer:save-preferences', preferences)
.then(() => toast.success('Preferences saved successfully'))
.then(() => dispatch(updatePreferences(preferences)))
.then(resolve)
.catch((err) => {
toast.error('An error occurred while saving preferences');
console.error(err);
reject(err);
});
});
};
export default appSlice.reducer; export default appSlice.reducer;

View File

@ -1057,7 +1057,6 @@ export const collectionsSlice = createSlice({
if (collection) { if (collection) {
collection.root = file.data; collection.root = file.data;
} }
console.log('collectionAddFileEvent', file);
return; return;
} }

View File

@ -8,10 +8,9 @@ const menuTemplate = require('./app/menu-template');
const LastOpenedCollections = require('./store/last-opened-collections'); const LastOpenedCollections = require('./store/last-opened-collections');
const registerNetworkIpc = require('./ipc/network'); const registerNetworkIpc = require('./ipc/network');
const registerCollectionsIpc = require('./ipc/collection'); const registerCollectionsIpc = require('./ipc/collection');
const registerApplicationIpc = require('./ipc/application'); const registerPreferencesIpc = require('./ipc/preferences');
const Watcher = require('./app/watcher'); const Watcher = require('./app/watcher');
const { loadWindowState, saveWindowState } = require('./utils/window'); const { loadWindowState, saveWindowState } = require('./utils/window');
const preferences = require('./store/preferences');
const lastOpenedCollections = new LastOpenedCollections(); const lastOpenedCollections = new LastOpenedCollections();
@ -41,8 +40,8 @@ app.on('ready', async () => {
y, y,
width, width,
height, height,
minWidth:1000, minWidth: 1000,
minHeight:640, minHeight: 640,
webPreferences: { webPreferences: {
nodeIntegration: true, nodeIntegration: true,
contextIsolation: true, contextIsolation: true,
@ -78,7 +77,7 @@ app.on('ready', async () => {
// register all ipc handlers // register all ipc handlers
registerNetworkIpc(mainWindow); registerNetworkIpc(mainWindow);
registerCollectionsIpc(mainWindow, watcher, lastOpenedCollections); registerCollectionsIpc(mainWindow, watcher, lastOpenedCollections);
registerApplicationIpc(mainWindow, preferences); registerPreferencesIpc(mainWindow, watcher, lastOpenedCollections);
}); });
// Quit the app once all windows are closed // Quit the app once all windows are closed

View File

@ -15,9 +15,10 @@ const {
sanitizeDirectoryName sanitizeDirectoryName
} = require('../utils/filesystem'); } = require('../utils/filesystem');
const { stringifyJson } = require('../utils/common'); const { stringifyJson } = require('../utils/common');
const { openCollectionDialog, openCollection } = require('../app/collections'); const { openCollectionDialog } = require('../app/collections');
const { generateUidBasedOnHash } = require('../utils/common'); const { generateUidBasedOnHash } = require('../utils/common');
const { moveRequestUid, deleteRequestUid } = require('../cache/requestUids'); const { moveRequestUid, deleteRequestUid } = require('../cache/requestUids');
const { setPreferences } = require('../store/preferences');
const EnvironmentSecretsStore = require('../store/env-secrets'); const EnvironmentSecretsStore = require('../store/env-secrets');
const environmentSecretsStore = new EnvironmentSecretsStore(); const environmentSecretsStore = new EnvironmentSecretsStore();
@ -462,21 +463,6 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
} }
}); });
ipcMain.handle('renderer:ready-collection', async (event) => {
// reload last opened collections
const lastOpened = lastOpenedCollections.getAll();
if (lastOpened && lastOpened.length) {
for (let collectionPath of lastOpened) {
if (isDirectory(collectionPath)) {
openCollection(mainWindow, watcher, collectionPath, {
dontSendDisplayErrors: true
});
}
}
}
});
ipcMain.handle('renderer:update-bruno-config', async (event, brunoConfig, collectionPath, collectionUid) => { ipcMain.handle('renderer:update-bruno-config', async (event, brunoConfig, collectionPath, collectionUid) => {
try { try {
const brunoConfigPath = path.join(collectionPath, 'bruno.json'); const brunoConfigPath = path.join(collectionPath, 'bruno.json');

View File

@ -0,0 +1,35 @@
const { ipcMain } = require('electron');
const { getPreferences, savePreferences } = require('../store/preferences');
const { isDirectory } = require('../utils/filesystem');
const { openCollection } = require('../app/collections');
const registerPreferencesIpc = (mainWindow, watcher, lastOpenedCollections) => {
ipcMain.handle('renderer:ready', async (event) => {
// load preferences
const preferences = getPreferences();
mainWindow.webContents.send('main:load-preferences', preferences);
// reload last opened collections
const lastOpened = lastOpenedCollections.getAll();
if (lastOpened && lastOpened.length) {
for (let collectionPath of lastOpened) {
if (isDirectory(collectionPath)) {
openCollection(mainWindow, watcher, collectionPath, {
dontSendDisplayErrors: true
});
}
}
}
});
ipcMain.handle('renderer:save-preferences', async (event, preferences) => {
try {
await savePreferences(preferences);
} catch (error) {
return Promise.reject(error);
}
});
};
module.exports = registerPreferencesIpc;

View File

@ -1,3 +1,4 @@
const Yup = require('yup');
const Store = require('electron-store'); const Store = require('electron-store');
const { get } = require('lodash'); const { get } = require('lodash');
@ -20,9 +21,12 @@ const { get } = require('lodash');
const defaultPreferences = { const defaultPreferences = {
request: { request: {
tlsVerification: true, sslVerification: true,
caCert: '' caCert: ''
}, },
font: {
codeFont: 'default'
},
proxy: { proxy: {
enabled: false, enabled: false,
protocol: 'http', protocol: 'http',
@ -37,6 +41,15 @@ const defaultPreferences = {
} }
}; };
const preferencesSchema = Yup.object().shape({
request: Yup.object().shape({
sslVerification: Yup.boolean()
}),
font: Yup.object().shape({
codeFont: Yup.string().nullable()
})
});
class PreferencesStore { class PreferencesStore {
constructor() { constructor() {
this.store = new Store({ this.store = new Store({
@ -45,25 +58,36 @@ class PreferencesStore {
}); });
} }
get(key) { getPreferences() {
return this.store.get(key); return {
defaultPreferences,
...this.store.get('preferences')
};
} }
set(key, value) { savePreferences(newPreferences) {
this.store.set(key, value); return this.store.set('preferences', newPreferences);
}
getPath() {
return this.store.path;
} }
} }
const preferencesStore = new PreferencesStore(); const preferencesStore = new PreferencesStore();
const getPreferences = () => { const getPreferences = () => {
return { return preferencesStore.getPreferences();
...defaultPreferences, };
...(preferencesStore.get('preferences') || {})
}; const savePreferences = async (newPreferences) => {
return new Promise((resolve, reject) => {
preferencesSchema
.validate(newPreferences, { abortEarly: true })
.then((validatedPreferences) => {
preferencesStore.savePreferences(validatedPreferences);
resolve();
})
.catch((error) => {
reject(error);
});
});
}; };
const preferences = { const preferences = {
@ -85,28 +109,11 @@ const preferences = {
getProxyConfig: () => { getProxyConfig: () => {
return get(getPreferences(), 'proxy', {}); return get(getPreferences(), 'proxy', {});
},
setPreferences: (validatedPreferences) => {
const updatedPreferences = {
...getPreferences(),
...validatedPreferences
};
preferencesStore.set('preferences', updatedPreferences);
},
migrateSslVerification: (sslVerification) => {
let preferences = getPreferences();
if (!preferences.request) {
const updatedPreferences = {
...preferences,
request: {
tlsVerification: sslVerification
}
};
preferencesStore.set('preferences', updatedPreferences);
}
} }
}; };
module.exports = preferences; module.exports = {
getPreferences,
savePreferences,
preferences
};

View File

@ -1,4 +1,3 @@
const _ = require('lodash');
const Store = require('electron-store'); const Store = require('electron-store');
const DEFAULT_WINDOW_WIDTH = 1280; const DEFAULT_WINDOW_WIDTH = 1280;