mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-11 09:51:30 +00:00
feat: preferences as tab (#6786)
* feat: preferences as tab refactor: remove preferences tab from permanent tabs and update tab label handling fix: comment Co-authored-by: Sid <siddharth@usebruno.com> * refactor: replace Checkbox component with native input elements in Preferences and ProxySettings --------- Co-authored-by: Sid <siddharth@usebruno.com>
This commit is contained in:
@@ -6,7 +6,7 @@ import { savePreferences } from 'providers/ReduxStore/slices/app';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
const Font = ({ close }) => {
|
||||
const Font = () => {
|
||||
const dispatch = useDispatch();
|
||||
const preferences = useSelector((state) => state.app.preferences);
|
||||
const isInitialMount = useRef(true);
|
||||
|
||||
@@ -2,6 +2,22 @@ import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
color: ${(props) => props.theme.text};
|
||||
|
||||
.text-link {
|
||||
color: ${(props) => props.theme.colors.text.link};
|
||||
text-decoration: none;
|
||||
font-size: 0.8125rem;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
form.bruno-form {
|
||||
label {
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
|
||||
@@ -11,7 +11,7 @@ import toast from 'react-hot-toast';
|
||||
import path from 'utils/common/path';
|
||||
import { IconTrash } from '@tabler/icons';
|
||||
|
||||
const General = ({ close }) => {
|
||||
const General = () => {
|
||||
const preferences = useSelector((state) => state.app.preferences);
|
||||
const dispatch = useDispatch();
|
||||
const inputFileCaCertificateRef = useRef();
|
||||
|
||||
@@ -2,12 +2,12 @@ import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
table {
|
||||
width: 100%;
|
||||
width: 80%;
|
||||
border-collapse: collapse;
|
||||
|
||||
thead,
|
||||
td {
|
||||
border: 2px solid ${(props) => props.theme.table.border};
|
||||
border: 1px solid ${(props) => props.theme.table.border};
|
||||
}
|
||||
|
||||
thead {
|
||||
@@ -17,7 +17,7 @@ const StyledWrapper = styled.div`
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 4px 8px;
|
||||
padding: 6px 10px;
|
||||
font-size: ${(props) => props.theme.font.size.sm};
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ const StyledWrapper = styled.div`
|
||||
font-weight: 500;
|
||||
padding: 10px;
|
||||
text-align: left;
|
||||
border: 1px solid ${(props) => props.theme.table.border};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,11 +36,13 @@ const StyledWrapper = styled.div`
|
||||
.key-button {
|
||||
display: inline-block;
|
||||
color: ${(props) => props.theme.table.input.color};
|
||||
opacity: 0.7;
|
||||
border-radius: 4px;
|
||||
padding: 1px 5px;
|
||||
font-family: monospace;
|
||||
margin-right: 8px;
|
||||
border: 1px solid #ccc;
|
||||
border-bottom: 1.44px solid ${(props) => props.theme.table.input.border};
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
@@ -291,12 +291,14 @@ const ProxySettings = ({ close }) => {
|
||||
Auth
|
||||
</label>
|
||||
<input
|
||||
id="config.auth.disabled"
|
||||
type="checkbox"
|
||||
name="config.auth.disabled"
|
||||
checked={!formik.values.config.auth.disabled}
|
||||
onChange={(e) => {
|
||||
formik.setFieldValue('config.auth.disabled', !e.target.checked);
|
||||
}}
|
||||
className="mousetrap mr-0"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -2,7 +2,7 @@ import styled from 'styled-components';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
div.tabs {
|
||||
padding: 8px;
|
||||
padding: 12px;
|
||||
min-width: 160px;
|
||||
|
||||
div.tab {
|
||||
@@ -28,10 +28,10 @@ const StyledWrapper = styled.div`
|
||||
|
||||
&.active {
|
||||
color: ${(props) => props.theme.text} !important;
|
||||
background: ${(props) => props.theme.modal.title.bg};
|
||||
background: ${(props) => props.theme.tabs.secondary.active.bg};
|
||||
|
||||
&:hover {
|
||||
background: ${(props) => props.theme.modal.title.bg} !important;
|
||||
background: ${(props) => props.theme.tabs.secondary.active.bg} !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,9 +39,9 @@ const StyledWrapper = styled.div`
|
||||
|
||||
section.tab-panel {
|
||||
min-height: 70vh;
|
||||
max-height: 70vh;
|
||||
overflow-y: auto;
|
||||
width: clamp(300px, 45vw, 550px);
|
||||
flex-grow: 1;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
input[type="checkbox"],
|
||||
@@ -50,6 +50,24 @@ const StyledWrapper = styled.div`
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.textbox {
|
||||
line-height: 1.5;
|
||||
padding: 0.45rem;
|
||||
border-radius: ${(props) => props.theme.border.radius.sm};
|
||||
background-color: ${(props) => props.theme.input.bg};
|
||||
border: 1px solid ${(props) => props.theme.input.border};
|
||||
color: ${(props) => props.theme.text};
|
||||
|
||||
&:focus {
|
||||
border: solid 1px ${(props) => props.theme.input.focusBorder} !important;
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
.section-header {
|
||||
font-size: ${(props) => props.theme.font.size.sm};
|
||||
color: ${(props) => props.theme.colors.text.muted};
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
import Modal from 'components/Modal/index';
|
||||
import classnames from 'classnames';
|
||||
import React, { useState } from 'react';
|
||||
import { IconSettings, IconPalette, IconBrowser, IconUserCircle, IconKeyboard, IconZoomQuestion, IconSquareLetterB } from '@tabler/icons';
|
||||
import React from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { updateActivePreferencesTab } from 'providers/ReduxStore/slices/app';
|
||||
import {
|
||||
IconSettings,
|
||||
IconPalette,
|
||||
IconBrowser,
|
||||
IconUserCircle,
|
||||
IconKeyboard,
|
||||
IconZoomQuestion,
|
||||
IconSquareLetterB
|
||||
} from '@tabler/icons';
|
||||
|
||||
import Support from './Support';
|
||||
import General from './General';
|
||||
@@ -13,8 +22,13 @@ import Beta from './Beta';
|
||||
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const Preferences = ({ onClose }) => {
|
||||
const [tab, setTab] = useState('general');
|
||||
const Preferences = () => {
|
||||
const dispatch = useDispatch();
|
||||
const tab = useSelector((state) => state.app.activePreferencesTab);
|
||||
|
||||
const setTab = (tab) => {
|
||||
dispatch(updateActivePreferencesTab({ tab }));
|
||||
};
|
||||
|
||||
const getTabClassname = (tabName) => {
|
||||
return classnames(`tab select-none ${tabName}`, {
|
||||
@@ -25,27 +39,27 @@ const Preferences = ({ onClose }) => {
|
||||
const getTabPanel = (tab) => {
|
||||
switch (tab) {
|
||||
case 'general': {
|
||||
return <General close={onClose} />;
|
||||
return <General />;
|
||||
}
|
||||
|
||||
case 'themes': {
|
||||
return <Themes close={onClose} />;
|
||||
return <Themes />;
|
||||
}
|
||||
|
||||
case 'proxy': {
|
||||
return <Proxy close={onClose} />;
|
||||
return <Proxy />;
|
||||
}
|
||||
|
||||
case 'display': {
|
||||
return <Display close={onClose} />;
|
||||
return <Display />;
|
||||
}
|
||||
|
||||
case 'keybindings': {
|
||||
return <Keybindings close={onClose} />;
|
||||
return <Keybindings />;
|
||||
}
|
||||
|
||||
case 'beta': {
|
||||
return <Beta close={onClose} />;
|
||||
return <Beta />;
|
||||
}
|
||||
|
||||
case 'support': {
|
||||
@@ -55,42 +69,47 @@ const Preferences = ({ onClose }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<Modal size="lg" title="Preferences" handleCancel={onClose} hideFooter={true}>
|
||||
<div className="flex flex-row gap-2 mx-[-1rem] !my-[-1.5rem] py-2">
|
||||
<div className="flex flex-col items-center tabs" role="tablist">
|
||||
<div className={getTabClassname('general')} role="tab" onClick={() => setTab('general')}>
|
||||
<IconSettings size={16} strokeWidth={1.5} />
|
||||
General
|
||||
</div>
|
||||
<div className={getTabClassname('themes')} role="tab" onClick={() => setTab('themes')}>
|
||||
<IconPalette size={16} strokeWidth={1.5} />
|
||||
Themes
|
||||
</div>
|
||||
<div className={getTabClassname('display')} role="tab" onClick={() => setTab('display')}>
|
||||
<IconBrowser size={16} strokeWidth={1.5} />
|
||||
Display
|
||||
</div>
|
||||
<div className={getTabClassname('proxy')} role="tab" onClick={() => setTab('proxy')}>
|
||||
<IconUserCircle size={16} strokeWidth={1.5} />
|
||||
Proxy
|
||||
</div>
|
||||
<div className={getTabClassname('keybindings')} role="tab" onClick={() => setTab('keybindings')}>
|
||||
<IconKeyboard size={16} strokeWidth={1.5} />
|
||||
Keybindings
|
||||
</div>
|
||||
<div className={getTabClassname('support')} role="tab" onClick={() => setTab('support')}>
|
||||
<IconZoomQuestion size={16} strokeWidth={1.5} />
|
||||
Support
|
||||
</div>
|
||||
<div className={getTabClassname('beta')} role="tab" onClick={() => setTab('beta')}>
|
||||
<IconSquareLetterB size={16} strokeWidth={1.5} />
|
||||
Beta
|
||||
</div>
|
||||
<StyledWrapper className="h-full">
|
||||
<div className="flex flex-row gap-2 h-full">
|
||||
<div className="flex flex-col items-center tabs tablist" role="tablist">
|
||||
<div className={getTabClassname('general')} role="tab" onClick={() => setTab('general')}>
|
||||
<IconSettings size={16} strokeWidth={1.5} />
|
||||
General
|
||||
</div>
|
||||
<div className={getTabClassname('themes')} role="tab" onClick={() => setTab('themes')}>
|
||||
<IconPalette size={16} strokeWidth={1.5} />
|
||||
Themes
|
||||
</div>
|
||||
<div className={getTabClassname('display')} role="tab" onClick={() => setTab('display')}>
|
||||
<IconBrowser size={16} strokeWidth={1.5} />
|
||||
Display
|
||||
</div>
|
||||
<div className={getTabClassname('proxy')} role="tab" onClick={() => setTab('proxy')}>
|
||||
<IconUserCircle size={16} strokeWidth={1.5} />
|
||||
Proxy
|
||||
</div>
|
||||
<div className={getTabClassname('keybindings')} role="tab" onClick={() => setTab('keybindings')}>
|
||||
<IconKeyboard size={16} strokeWidth={1.5} />
|
||||
Keybindings
|
||||
</div>
|
||||
<div className={getTabClassname('support')} role="tab" onClick={() => setTab('support')}>
|
||||
<IconZoomQuestion size={16} strokeWidth={1.5} />
|
||||
Support
|
||||
</div>
|
||||
<div className={getTabClassname('beta')} role="tab" onClick={() => setTab('beta')}>
|
||||
<IconSquareLetterB size={16} strokeWidth={1.5} />
|
||||
Beta
|
||||
</div>
|
||||
<section className="flex flex-grow ps-2 pe-4 pt-2 pb-6 tab-panel">{getTabPanel(tab)}</section>
|
||||
</div>
|
||||
</Modal>
|
||||
<section
|
||||
className="flex flex-grow ps-2 pe-4 pt-2 pb-6 p-[12px] tab-panel"
|
||||
role="tabpanel"
|
||||
id={`${tab}-panel`}
|
||||
aria-labelledby={`${tab}-tab`}
|
||||
>
|
||||
{getTabPanel(tab)}
|
||||
</section>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -33,6 +33,7 @@ import WSResponsePane from 'components/ResponsePane/WsResponsePane';
|
||||
import { useTabPaneBoundaries } from 'hooks/useTabPaneBoundaries/index';
|
||||
import ResponseExample from 'components/ResponseExample';
|
||||
import WorkspaceHome from 'components/WorkspaceHome';
|
||||
import Preferences from 'components/Preferences';
|
||||
import EnvironmentSettings from 'components/Environments/EnvironmentSettings';
|
||||
import GlobalEnvironmentSettings from 'components/Environments/GlobalEnvironmentSettings';
|
||||
|
||||
@@ -171,13 +172,17 @@ const RequestTabPanel = () => {
|
||||
}, [isConsoleOpen, isVerticalLayout]);
|
||||
|
||||
if (!activeTabUid || !focusedTab) {
|
||||
return <WorkspaceHome />;
|
||||
return <div className="pb-4 px-4">An error occurred!</div>;
|
||||
}
|
||||
|
||||
if (focusedTab.type === 'global-environment-settings') {
|
||||
return <GlobalEnvironmentSettings />;
|
||||
}
|
||||
|
||||
if (focusedTab.type === 'preferences') {
|
||||
return <Preferences />;
|
||||
}
|
||||
|
||||
if (!focusedTab.uid || !focusedTab.collectionUid) {
|
||||
return <div className="pb-4 px-4">An error occurred!</div>;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,10 @@ import ActionIcon from 'ui/ActionIcon';
|
||||
const CollectionToolBar = ({ collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
if (!collection) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleRun = () => {
|
||||
dispatch(
|
||||
addTab({
|
||||
|
||||
@@ -61,6 +61,14 @@ const SpecialTab = ({ handleCloseClick, type, tabName, handleDoubleClick, hasDra
|
||||
</>
|
||||
);
|
||||
}
|
||||
case 'preferences': {
|
||||
return (
|
||||
<>
|
||||
<IconSettings size={14} strokeWidth={1.5} className="special-tab-icon flex-shrink-0" />
|
||||
<span className="ml-1 tab-name">Preferences</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -172,7 +172,7 @@ const RequestTab = ({ tab, collection, tabIndex, collectionRequestTabs, folderUi
|
||||
setShowConfirmGlobalEnvironmentClose(true);
|
||||
};
|
||||
|
||||
if (['collection-settings', 'collection-overview', 'folder-settings', 'variables', 'collection-runner', 'environment-settings', 'global-environment-settings'].includes(tab.type)) {
|
||||
if (['collection-settings', 'collection-overview', 'folder-settings', 'variables', 'collection-runner', 'environment-settings', 'global-environment-settings', 'preferences'].includes(tab.type)) {
|
||||
return (
|
||||
<StyledWrapper
|
||||
className={`flex items-center justify-between tab-container px-2 ${tab.preview ? 'italic' : ''}`}
|
||||
|
||||
@@ -44,7 +44,7 @@ const RequestTabs = () => {
|
||||
}, []);
|
||||
|
||||
const activeTab = find(tabs, (t) => t.uid === activeTabUid);
|
||||
const activeCollection = find(collections, (c) => c.uid === activeTab?.collectionUid);
|
||||
const activeCollection = find(collections, (c) => c?.uid === activeTab?.collectionUid);
|
||||
const collectionRequestTabs = filter(tabs, (t) => t.collectionUid === activeTab?.collectionUid);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -52,7 +52,7 @@ const RequestTabs = () => {
|
||||
|
||||
const checkOverflow = () => {
|
||||
if (tabsRef.current && scrollContainerRef.current) {
|
||||
const hasOverflow = tabsRef.current.scrollWidth > scrollContainerRef.current.clientWidth;
|
||||
const hasOverflow = tabsRef.current.scrollWidth > scrollContainerRef.current.clientWidth + 1;
|
||||
setShowChevrons(hasOverflow);
|
||||
}
|
||||
};
|
||||
@@ -111,7 +111,7 @@ const RequestTabs = () => {
|
||||
)}
|
||||
{collectionRequestTabs && collectionRequestTabs.length ? (
|
||||
<>
|
||||
<CollectionToolBar collection={activeCollection} />
|
||||
{activeCollection && <CollectionToolBar collection={activeCollection} />}
|
||||
<div className="flex items-center gap-2 pl-2" ref={collectionTabsRef}>
|
||||
<div className={classnames('scroll-chevrons', { hidden: !showChevrons })}>
|
||||
<ActionIcon size="lg" onClick={leftSlide} aria-label="Left Chevron" style={{ marginBottom: '3px' }}>
|
||||
|
||||
@@ -1,22 +1,29 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import find from 'lodash/find';
|
||||
import { IconSettings, IconCookie, IconTool, IconSearch, IconPalette, IconBrandGithub } from '@tabler/icons';
|
||||
import Mousetrap from 'mousetrap';
|
||||
import { getKeyBindingsForActionAllOS } from 'providers/Hotkeys/keyMappings';
|
||||
import ToolHint from 'components/ToolHint';
|
||||
import Preferences from 'components/Preferences';
|
||||
import Cookies from 'components/Cookies';
|
||||
import Notifications from 'components/Notifications';
|
||||
import Portal from 'components/Portal';
|
||||
import ThemeDropdown from './ThemeDropdown';
|
||||
import { showPreferences } from 'providers/ReduxStore/slices/app';
|
||||
import { openConsole } from 'providers/ReduxStore/slices/logs';
|
||||
import { setActiveWorkspaceTab } from 'providers/ReduxStore/slices/workspaceTabs';
|
||||
import { addTab } from 'providers/ReduxStore/slices/tabs';
|
||||
import { useApp } from 'providers/App';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const StatusBar = () => {
|
||||
const dispatch = useDispatch();
|
||||
const preferencesOpen = useSelector((state) => state.app.showPreferences);
|
||||
const activeWorkspaceUid = useSelector((state) => state.workspaces.activeWorkspaceUid);
|
||||
const showHomePage = useSelector((state) => state.app.showHomePage);
|
||||
const showManageWorkspacePage = useSelector((state) => state.app.showManageWorkspacePage);
|
||||
const showApiSpecPage = useSelector((state) => state.app.showApiSpecPage);
|
||||
const tabs = useSelector((state) => state.tabs.tabs);
|
||||
const activeTabUid = useSelector((state) => state.tabs.activeTabUid);
|
||||
const activeTab = find(tabs, (t) => t.uid === activeTabUid);
|
||||
const logs = useSelector((state) => state.logs.logs);
|
||||
const [cookiesOpen, setCookiesOpen] = useState(false);
|
||||
const { version } = useApp();
|
||||
@@ -27,6 +34,22 @@ const StatusBar = () => {
|
||||
dispatch(openConsole());
|
||||
};
|
||||
|
||||
const handlePreferencesClick = () => {
|
||||
if (showHomePage || showManageWorkspacePage || showApiSpecPage || !activeTabUid) {
|
||||
if (activeWorkspaceUid) {
|
||||
dispatch(setActiveWorkspaceTab({ workspaceUid: activeWorkspaceUid, type: 'preferences' }));
|
||||
}
|
||||
} else {
|
||||
dispatch(
|
||||
addTab({
|
||||
type: 'preferences',
|
||||
uid: activeTab?.collectionUid ? `${activeTab.collectionUid}-preferences` : 'preferences',
|
||||
collectionUid: activeTab?.collectionUid
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const openGlobalSearch = () => {
|
||||
const bindings = getKeyBindingsForActionAllOS('globalSearch') || [];
|
||||
bindings.forEach((binding) => {
|
||||
@@ -36,21 +59,6 @@ const StatusBar = () => {
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
{preferencesOpen && (
|
||||
<Portal>
|
||||
<Preferences
|
||||
onClose={() => {
|
||||
dispatch(showPreferences(false));
|
||||
document.querySelector('[data-trigger="preferences"]').focus();
|
||||
}}
|
||||
aria-modal="true"
|
||||
role="dialog"
|
||||
aria-labelledby="preferences-title"
|
||||
aria-describedby="preferences-description"
|
||||
/>
|
||||
</Portal>
|
||||
)}
|
||||
|
||||
{cookiesOpen && (
|
||||
<Portal>
|
||||
<Cookies
|
||||
@@ -73,7 +81,7 @@ const StatusBar = () => {
|
||||
<button
|
||||
className="status-bar-button preferences-button"
|
||||
data-trigger="preferences"
|
||||
onClick={() => dispatch(showPreferences(true))}
|
||||
onClick={handlePreferencesClick}
|
||||
tabIndex={0}
|
||||
aria-label="Open Preferences"
|
||||
>
|
||||
|
||||
@@ -22,7 +22,7 @@ const StyledWrapper = styled.div`
|
||||
table tr {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
|
||||
table th {
|
||||
position: relative;
|
||||
font-weight: 400;
|
||||
|
||||
@@ -7,6 +7,7 @@ import toast from 'react-hot-toast';
|
||||
import CloseWorkspace from 'components/Sidebar/CloseWorkspace';
|
||||
import WorkspaceOverview from './WorkspaceOverview';
|
||||
import WorkspaceEnvironments from './WorkspaceEnvironments';
|
||||
import Preferences from 'components/Preferences';
|
||||
import WorkspaceTabs from 'components/WorkspaceTabs';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import Dropdown from 'components/Dropdown';
|
||||
@@ -157,6 +158,8 @@ const WorkspaceHome = () => {
|
||||
return <WorkspaceOverview workspace={activeWorkspace} />;
|
||||
case 'environments':
|
||||
return <WorkspaceEnvironments workspace={activeWorkspace} />;
|
||||
case 'preferences':
|
||||
return <Preferences />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import React from 'react';
|
||||
import { IconX, IconHome, IconWorld } from '@tabler/icons';
|
||||
import { IconX, IconHome, IconWorld, IconSettings } from '@tabler/icons';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { closeWorkspaceTab } from 'providers/ReduxStore/slices/workspaceTabs';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const TAB_ICONS = {
|
||||
overview: IconHome,
|
||||
environments: IconWorld
|
||||
environments: IconWorld,
|
||||
preferences: IconSettings
|
||||
};
|
||||
|
||||
const WorkspaceTab = ({ tab, isActive }) => {
|
||||
|
||||
@@ -57,7 +57,7 @@ const WorkspaceTabs = ({ workspaceUid }) => {
|
||||
|
||||
const checkOverflow = () => {
|
||||
if (tabsRef.current && scrollContainerRef.current) {
|
||||
const hasOverflow = tabsRef.current.scrollWidth > scrollContainerRef.current.clientWidth;
|
||||
const hasOverflow = tabsRef.current.scrollWidth > scrollContainerRef.current.clientWidth + 1;
|
||||
setShowChevrons(hasOverflow);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -123,7 +123,7 @@ export default function Main() {
|
||||
<ApiSpecPanel key={activeApiSpecUid} />
|
||||
) : showManageWorkspacePage ? (
|
||||
<ManageWorkspace />
|
||||
) : showHomePage ? (
|
||||
) : showHomePage || !activeTabUid ? (
|
||||
<WorkspaceHome />
|
||||
) : (
|
||||
<>
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import { useEffect } from 'react';
|
||||
import {
|
||||
showPreferences,
|
||||
updateCookies,
|
||||
updatePreferences,
|
||||
updateSystemProxyEnvVariables
|
||||
} from 'providers/ReduxStore/slices/app';
|
||||
import {
|
||||
addTab
|
||||
} from 'providers/ReduxStore/slices/tabs';
|
||||
import {
|
||||
setActiveWorkspaceTab
|
||||
} from 'providers/ReduxStore/slices/workspaceTabs';
|
||||
import {
|
||||
brunoConfigUpdateEvent,
|
||||
collectionAddDirectoryEvent,
|
||||
@@ -26,7 +31,7 @@ import { collectionAddEnvFileEvent, openCollectionEvent, hydrateCollectionWithUi
|
||||
import { workspaceOpenedEvent, workspaceConfigUpdatedEvent } from 'providers/ReduxStore/slices/workspaces/actions';
|
||||
import { workspaceDotEnvUpdateEvent } from 'providers/ReduxStore/slices/workspaces';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useDispatch, useStore } from 'react-redux';
|
||||
import { isElectron } from 'utils/common/platform';
|
||||
import { globalEnvironmentsUpdateEvent, updateGlobalEnvironments } from 'providers/ReduxStore/slices/global-environments';
|
||||
import { collectionAddOauth2CredentialsByUrl, updateCollectionLoadingState } from 'providers/ReduxStore/slices/collections/index';
|
||||
@@ -36,6 +41,7 @@ import { apiSpecAddFileEvent, apiSpecChangeFileEvent } from 'providers/ReduxStor
|
||||
|
||||
const useIpcEvents = () => {
|
||||
const dispatch = useDispatch();
|
||||
const store = useStore();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isElectron()) {
|
||||
@@ -126,7 +132,7 @@ const useIpcEvents = () => {
|
||||
});
|
||||
|
||||
const removeWorkspaceEnvironmentAddedListener = ipcRenderer.on('main:workspace-environment-added', (workspaceUid, file) => {
|
||||
const state = window.__store__.getState();
|
||||
const state = store.getState();
|
||||
const activeWorkspaceUid = state.workspaces?.activeWorkspaceUid;
|
||||
if (activeWorkspaceUid === workspaceUid) {
|
||||
const workspace = state.workspaces?.workspaces?.find((w) => w.uid === workspaceUid);
|
||||
@@ -144,7 +150,7 @@ const useIpcEvents = () => {
|
||||
});
|
||||
|
||||
const removeWorkspaceEnvironmentChangedListener = ipcRenderer.on('main:workspace-environment-changed', (workspaceUid, file) => {
|
||||
const state = window.__store__.getState();
|
||||
const state = store.getState();
|
||||
const activeWorkspaceUid = state.workspaces?.activeWorkspaceUid;
|
||||
if (activeWorkspaceUid === workspaceUid) {
|
||||
const workspace = state.workspaces?.workspaces?.find((w) => w.uid === workspaceUid);
|
||||
@@ -162,7 +168,7 @@ const useIpcEvents = () => {
|
||||
});
|
||||
|
||||
const removeWorkspaceEnvironmentDeletedListener = ipcRenderer.on('main:workspace-environment-deleted', (workspaceUid, environmentUid) => {
|
||||
const state = window.__store__.getState();
|
||||
const state = store.getState();
|
||||
const activeWorkspaceUid = state.workspaces?.activeWorkspaceUid;
|
||||
if (activeWorkspaceUid === workspaceUid) {
|
||||
const workspace = state.workspaces?.workspaces?.find((w) => w.uid === workspaceUid);
|
||||
@@ -239,7 +245,26 @@ const useIpcEvents = () => {
|
||||
);
|
||||
|
||||
const removeShowPreferencesListener = ipcRenderer.on('main:open-preferences', () => {
|
||||
dispatch(showPreferences(true));
|
||||
const state = store.getState();
|
||||
const activeWorkspaceUid = state.workspaces?.activeWorkspaceUid;
|
||||
const { showHomePage, showManageWorkspacePage, showApiSpecPage } = state.app;
|
||||
const tabs = state.tabs?.tabs;
|
||||
const activeTabUid = state.tabs?.activeTabUid;
|
||||
const activeTab = tabs?.find((t) => t.uid === activeTabUid);
|
||||
|
||||
if (showHomePage || showManageWorkspacePage || showApiSpecPage || !activeTabUid) {
|
||||
if (activeWorkspaceUid) {
|
||||
dispatch(setActiveWorkspaceTab({ workspaceUid: activeWorkspaceUid, type: 'preferences' }));
|
||||
}
|
||||
} else {
|
||||
dispatch(
|
||||
addTab({
|
||||
type: 'preferences',
|
||||
uid: activeTab?.collectionUid ? `${activeTab.collectionUid}-preferences` : 'preferences',
|
||||
collectionUid: activeTab?.collectionUid
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const removePreferencesUpdatesListener = ipcRenderer.on('main:load-preferences', (val) => {
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
} from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { findCollectionByUid, findItemInCollection } from 'utils/collections';
|
||||
import { addTab, closeTabs, reorderTabs, switchTab } from 'providers/ReduxStore/slices/tabs';
|
||||
import { closeWorkspaceTab } from 'providers/ReduxStore/slices/workspaceTabs';
|
||||
import { toggleSidebarCollapse } from 'providers/ReduxStore/slices/app';
|
||||
import { getKeyBindingsForActionAllOS } from './keyMappings';
|
||||
|
||||
@@ -25,6 +26,8 @@ export const HotkeysProvider = (props) => {
|
||||
const tabs = useSelector((state) => state.tabs.tabs);
|
||||
const collections = useSelector((state) => state.collections.collections);
|
||||
const activeTabUid = useSelector((state) => state.tabs.activeTabUid);
|
||||
const showHomePage = useSelector((state) => state.app.showHomePage);
|
||||
const activeWorkspaceTabUid = useSelector((state) => state.workspaceTabs.activeTabUid);
|
||||
const [showNewRequestModal, setShowNewRequestModal] = useState(false);
|
||||
const [showGlobalSearchModal, setShowGlobalSearchModal] = useState(false);
|
||||
|
||||
@@ -171,11 +174,15 @@ export const HotkeysProvider = (props) => {
|
||||
// close tab hotkey
|
||||
useEffect(() => {
|
||||
Mousetrap.bind([...getKeyBindingsForActionAllOS('closeTab')], (e) => {
|
||||
dispatch(
|
||||
closeTabs({
|
||||
tabUids: [activeTabUid]
|
||||
})
|
||||
);
|
||||
if (showHomePage && activeWorkspaceTabUid) {
|
||||
dispatch(closeWorkspaceTab({ uid: activeWorkspaceTabUid }));
|
||||
} else if (activeTabUid) {
|
||||
dispatch(
|
||||
closeTabs({
|
||||
tabUids: [activeTabUid]
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return false; // this stops the event bubbling
|
||||
});
|
||||
@@ -183,7 +190,7 @@ export const HotkeysProvider = (props) => {
|
||||
return () => {
|
||||
Mousetrap.unbind([...getKeyBindingsForActionAllOS('closeTab')]);
|
||||
};
|
||||
}, [activeTabUid]);
|
||||
}, [activeTabUid, showHomePage, activeWorkspaceTabUid]);
|
||||
|
||||
// Switch to the previous tab
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import filter from 'lodash/filter';
|
||||
import brunoClipboard from 'utils/bruno-clipboard';
|
||||
import { addTab, focusTab } from './tabs';
|
||||
import { addTab, focusTab, closeTabs } from './tabs';
|
||||
|
||||
const initialState = {
|
||||
isDragging: false,
|
||||
@@ -10,11 +10,11 @@ const initialState = {
|
||||
sidebarCollapsed: false,
|
||||
screenWidth: 500,
|
||||
showHomePage: false,
|
||||
showPreferences: false,
|
||||
showApiSpecPage: false,
|
||||
showManageWorkspacePage: false,
|
||||
isEnvironmentSettingsModalOpen: false,
|
||||
isGlobalEnvironmentSettingsModalOpen: false,
|
||||
activePreferencesTab: 'general',
|
||||
preferences: {
|
||||
request: {
|
||||
sslVerification: true,
|
||||
@@ -88,18 +88,17 @@ export const appSlice = createSlice({
|
||||
},
|
||||
showApiSpecPage: (state) => {
|
||||
state.showHomePage = false;
|
||||
state.showPreferences = false;
|
||||
state.showApiSpecPage = true;
|
||||
},
|
||||
hideApiSpecPage: (state) => {
|
||||
state.showApiSpecPage = false;
|
||||
},
|
||||
showPreferences: (state, action) => {
|
||||
state.showPreferences = action.payload;
|
||||
},
|
||||
updatePreferences: (state, action) => {
|
||||
state.preferences = action.payload;
|
||||
},
|
||||
updateActivePreferencesTab: (state, action) => {
|
||||
state.activePreferencesTab = action.payload.tab;
|
||||
},
|
||||
updateCookies: (state, action) => {
|
||||
state.cookies = action.payload;
|
||||
},
|
||||
@@ -156,8 +155,8 @@ export const {
|
||||
hideManageWorkspacePage,
|
||||
showApiSpecPage,
|
||||
hideApiSpecPage,
|
||||
showPreferences,
|
||||
updatePreferences,
|
||||
updateActivePreferencesTab,
|
||||
updateCookies,
|
||||
insertTaskIntoQueue,
|
||||
removeTaskFromQueue,
|
||||
|
||||
@@ -25,7 +25,8 @@ export const tabsSlice = createSlice({
|
||||
'variables',
|
||||
'collection-runner',
|
||||
'environment-settings',
|
||||
'global-environment-settings'
|
||||
'global-environment-settings',
|
||||
'preferences'
|
||||
];
|
||||
|
||||
const existingTab = find(state.tabs, (tab) => tab.uid === uid);
|
||||
|
||||
@@ -164,11 +164,16 @@ export const workspaceTabsSlice = createSlice({
|
||||
|
||||
if (!tab) {
|
||||
const newTabUid = `${workspaceUid}-${type}`;
|
||||
const labels = {
|
||||
overview: 'Overview',
|
||||
environments: 'Global Environments',
|
||||
preferences: 'Preferences'
|
||||
};
|
||||
const newTab = {
|
||||
uid: newTabUid,
|
||||
workspaceUid,
|
||||
type,
|
||||
label: type === 'overview' ? 'Overview' : type,
|
||||
label: labels[type] || type,
|
||||
permanent: false
|
||||
};
|
||||
state.tabs.push(newTab);
|
||||
|
||||
@@ -115,6 +115,9 @@ export const findParentItemInCollectionByPathname = (collection, pathname) => {
|
||||
};
|
||||
|
||||
export const findItemInCollection = (collection, itemUid) => {
|
||||
if (!collection || !collection.items) {
|
||||
return null;
|
||||
}
|
||||
let flattenedItems = flattenItems(collection.items);
|
||||
|
||||
return findItem(flattenedItems, itemUid);
|
||||
|
||||
Reference in New Issue
Block a user