fixes: comments

This commit is contained in:
naman-bruno
2025-12-03 13:36:02 +05:30
parent 72d5411df8
commit c7be4775b3
34 changed files with 304 additions and 412 deletions

View File

@@ -33,7 +33,7 @@ import WSRequestPane from 'components/RequestPane/WSRequestPane';
import WSResponsePane from 'components/ResponsePane/WsResponsePane';
import { useTabPaneBoundaries } from 'hooks/useTabPaneBoundaries/index';
import ResponseExample from 'components/ResponseExample';
import WorkspaceOverview from 'components/WorkspaceOverview/index';
import WorkspaceHome from 'components/WorkspaceHome';
const MIN_LEFT_PANE_WIDTH = 300;
const MIN_RIGHT_PANE_WIDTH = 350;
@@ -137,7 +137,7 @@ const RequestTabPanel = () => {
}, [dragging]);
if (!activeTabUid) {
return <WorkspaceOverview />;
return <WorkspaceHome />;
}
if (!focusedTab || !focusedTab.uid || !focusedTab.collectionUid) {

View File

@@ -0,0 +1,40 @@
import { IconSearch, IconX } from '@tabler/icons';
const CollectionSearch = ({ searchText, setSearchText }) => {
return (
<div className="relative collection-filter px-2">
<div className="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
<span className="text-gray-500 sm:text-sm">
<IconSearch size={16} strokeWidth={1.5} />
</span>
</div>
<input
type="text"
name="search"
placeholder="Search requests …"
id="search"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
className="block w-full pl-7 pr-8 py-1 sm:text-sm"
value={searchText}
onChange={(e) => setSearchText(e.target.value.toLowerCase())}
/>
{searchText !== '' && (
<div className="absolute inset-y-0 right-0 pr-4 flex items-center">
<span
className="close-icon"
onClick={() => {
setSearchText('');
}}
>
<IconX size={16} strokeWidth={1.5} className="cursor-pointer" />
</span>
</div>
)}
</div>
);
};
export default CollectionSearch;

View File

@@ -48,8 +48,6 @@ const CreateOrOpenCollection = () => {
{createCollectionModalOpen ? (
<CreateCollection
onClose={() => setCreateCollectionModalOpen(false)}
workspaceUid={activeWorkspaceUid}
hideLocationInput={activeWorkspace && activeWorkspace.type !== 'default'}
/>
) : null}

View File

@@ -1,10 +1,10 @@
import React, { useState } from 'react';
import { useSelector } from 'react-redux';
import { IconSearch, IconX } from '@tabler/icons';
import Collection from '../Collections/Collection';
import Collection from './Collection';
import CreateCollection from '../CreateCollection';
import StyledWrapper from './StyledWrapper';
import CreateOrOpenCollection from './CreateOrOpenCollection';
import CollectionSearch from './CollectionSearch/index';
const Collections = ({ showSearch }) => {
const [searchText, setSearchText] = useState('');
@@ -14,27 +14,16 @@ const Collections = ({ showSearch }) => {
const activeWorkspace = workspaces.find((w) => w.uid === activeWorkspaceUid) || workspaces.find((w) => w.type === 'default');
let allCollections = [];
let workspaceCollections = [];
if (!activeWorkspace || activeWorkspace.type === 'default') {
if (activeWorkspace && activeWorkspace.collections && activeWorkspace.collections.length > 0) {
allCollections = activeWorkspace.collections.map((wc) => {
const loadedCollection = collections.find((c) => c.pathname === wc.path);
return loadedCollection;
}).filter(Boolean);
} else {
allCollections = [];
}
} else {
if (activeWorkspace.collections && activeWorkspace.collections.length > 0) {
allCollections = activeWorkspace.collections.map((wc) => {
const loadedCollection = collections.find((c) => c.pathname === wc.path);
return loadedCollection;
}).filter(Boolean);
}
if (activeWorkspace?.collections?.length) {
workspaceCollections = activeWorkspace.collections.map((wc) => {
return collections.find((c) => c.pathname === wc.path);
}).filter(Boolean);
}
if (!allCollections || !allCollections.length) {
if (!workspaceCollections || !workspaceCollections.length) {
return (
<StyledWrapper>
<CreateOrOpenCollection />
@@ -47,50 +36,16 @@ const Collections = ({ showSearch }) => {
{createCollectionModalOpen ? (
<CreateCollection
onClose={() => setCreateCollectionModalOpen(false)}
workspaceUid={activeWorkspace?.uid}
defaultLocation={activeWorkspace?.pathname ? `${activeWorkspace.pathname}/collections` : ''}
hideLocationInput={!!activeWorkspace?.pathname}
/>
) : null}
{showSearch && (
<div className="relative collection-filter px-2">
<div className="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
<span className="text-gray-500 sm:text-sm">
<IconSearch size={16} strokeWidth={1.5} />
</span>
</div>
<input
type="text"
name="search"
placeholder="Search requests …"
id="search"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
className="block w-full pl-7 pr-8 py-1 sm:text-sm"
value={searchText}
onChange={(e) => setSearchText(e.target.value.toLowerCase())}
/>
{searchText !== '' && (
<div className="absolute inset-y-0 right-0 pr-4 flex items-center">
<span
className="close-icon"
onClick={() => {
setSearchText('');
}}
>
<IconX size={16} strokeWidth={1.5} className="cursor-pointer" />
</span>
</div>
)}
</div>
<CollectionSearch searchText={searchText} setSearchText={setSearchText} />
)}
<div className={`mt-4 flex flex-col overflow-hidden hover:overflow-y-auto absolute ${showSearch ? 'top-16' : 'top-8'} bottom-0 left-0 right-0`}>
{allCollections && allCollections.length
? allCollections.map((c) => {
{workspaceCollections && workspaceCollections.length
? workspaceCollections.map((c) => {
return (
<Collection searchText={searchText} collection={c} key={c.uid} />
);

View File

@@ -4,7 +4,6 @@ import { useFormik } from 'formik';
import * as Yup from 'yup';
import path from 'path';
import { browseDirectory, createCollection } from 'providers/ReduxStore/slices/collections/actions';
import { loadWorkspaceCollections } from 'providers/ReduxStore/slices/workspaces/actions';
import toast from 'react-hot-toast';
import Portal from 'components/Portal';
import Modal from 'components/Modal';
@@ -20,21 +19,27 @@ import Dropdown from 'components/Dropdown';
import StyledWrapper from './StyledWrapper';
import get from 'lodash/get';
const CreateCollection = ({ onClose, workspaceUid, defaultLocation: propDefaultLocation, hideLocationInput = false }) => {
const CreateCollection = ({ onClose, defaultLocation: propDefaultLocation }) => {
const inputRef = useRef();
const dispatch = useDispatch();
const workspaces = useSelector((state) => state.workspaces?.workspaces || []);
const workspaceUid = useSelector((state) => state.workspaces?.activeWorkspaceUid);
const [isEditing, toggleEditing] = useState(false);
const preferences = useSelector((state) => state.app.preferences);
const [showExternalLocation, setShowExternalLocation] = useState(false);
const [showAdvanced, setShowAdvanced] = useState(false);
const defaultLocation = propDefaultLocation || get(preferences, 'general.defaultCollectionLocation', '');
const dropdownTippyRef = useRef();
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
const activeWorkspace = workspaces.find((w) => w.uid === workspaceUid);
const isDefaultWorkspace = activeWorkspace?.type === 'default';
const hideLocationInput = activeWorkspace && activeWorkspace.type !== 'default' && !!activeWorkspace?.pathname;
const defaultLocation = propDefaultLocation
|| (activeWorkspace?.pathname ? `${activeWorkspace.pathname}/collections` : '')
|| get(preferences, 'general.defaultCollectionLocation', '');
const shouldShowAccordion = workspaceUid && hideLocationInput && !isDefaultWorkspace;
const actuallyHideLocationInput = hideLocationInput && !showExternalLocation && !isDefaultWorkspace;
@@ -87,7 +92,6 @@ const CreateCollection = ({ onClose, workspaceUid, defaultLocation: propDefaultL
path: collectionPath
};
await ipcRenderer.invoke('renderer:add-collection-to-workspace', currentWorkspace.pathname, workspaceCollection);
await dispatch(loadWorkspaceCollections(workspaceUid));
}
toast.success('Collection created!');

View File

@@ -6,32 +6,13 @@ const StyledWrapper = styled.div`
align-items: center;
}
.workspace-icon-container {
display: flex;
align-items: center;
justify-content: center;
}
.workspace-letter-logo {
width: 20px;
height: 20px;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 500;
background: white;
color: #5d5d5d;
}
.workspace-name-container {
display: flex;
align-items: center;
gap: 4px;
padding: 6px 10px;
margin-left: 0px;
border-radius: 6px;
border-radius: ${(props) => props.theme.border.radius.base};
cursor: pointer;
transition: all 0.2s ease;
min-width: 0;
@@ -43,7 +24,7 @@ const StyledWrapper = styled.div`
}
.workspace-name {
font-size: 13px;
font-size: ${(props) => props.theme.font.size.base};
font-weight: 600;
color: ${(props) => props.theme.sidebar.color};
white-space: nowrap;
@@ -103,7 +84,7 @@ const StyledWrapper = styled.div`
.workspace-name {
flex: 1;
min-width: 0;
font-size: 13px;
font-size: ${(props) => props.theme.font.size.base};
font-weight: 400;
color: ${(props) => props.theme.dropdown.color};
white-space: nowrap;

View File

@@ -0,0 +1,136 @@
import { useState, forwardRef, useRef, useMemo, useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import toast from 'react-hot-toast';
import { IconPlus, IconChevronDown, IconCheck, IconFolder, IconPin, IconPinned } from '@tabler/icons';
import { savePreferences } from 'providers/ReduxStore/slices/app';
import { switchWorkspace, openWorkspaceDialog } from 'providers/ReduxStore/slices/workspaces/actions';
import { sortWorkspaces, toggleWorkspacePin } from 'utils/workspaces';
import Dropdown from 'components/Dropdown';
import CreateWorkspace from 'components/WorkspaceSidebar/CreateWorkspace';
const WorkspaceSelector = () => {
const dispatch = useDispatch();
const { workspaces, activeWorkspaceUid } = useSelector((state) => state.workspaces);
const preferences = useSelector((state) => state.app.preferences);
const activeWorkspace = workspaces.find((w) => w.uid === activeWorkspaceUid);
const sortedWorkspaces = useMemo(() => {
return sortWorkspaces(workspaces, preferences);
}, [workspaces, preferences]);
const [showDropdown, setShowDropdown] = useState(false);
const [createWorkspaceModalOpen, setCreateWorkspaceModalOpen] = useState(false);
const dropdownTippyRef = useRef();
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
const toTitleCase = (str) => {
if (!str) return '';
if (str === 'default') return 'Default';
return str
.split(/[\s-_]+/)
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join(' ');
};
const WorkspaceName = forwardRef((props, ref) => {
return (
<div ref={ref} className="workspace-name-container" onClick={() => setShowDropdown(!showDropdown)}>
<span className="workspace-name">{toTitleCase(activeWorkspace?.name) || 'Default Workspace'}</span>
<IconChevronDown size={14} stroke={1.5} className="chevron-icon" />
</div>
);
});
const handleWorkspaceSwitch = (workspaceUid) => {
dispatch(switchWorkspace(workspaceUid));
setShowDropdown(false);
toast.success(`Switched to ${workspaces.find((w) => w.uid === workspaceUid)?.name}`);
};
const handleOpenWorkspace = async () => {
setShowDropdown(false);
try {
await dispatch(openWorkspaceDialog());
toast.success('Workspace opened successfully');
} catch (error) {
toast.error(error.message || 'Failed to open workspace');
}
};
const handleCreateWorkspace = () => {
setShowDropdown(false);
setCreateWorkspaceModalOpen(true);
};
const handlePinWorkspace = useCallback((workspaceUid, e) => {
e.preventDefault();
e.stopPropagation();
const newPreferences = toggleWorkspacePin(workspaceUid, preferences);
dispatch(savePreferences(newPreferences));
}, [dispatch, preferences]);
return (
<>
{createWorkspaceModalOpen && (
<CreateWorkspace onClose={() => setCreateWorkspaceModalOpen(false)} />
)}
<Dropdown
onCreate={onDropdownCreate}
icon={<WorkspaceName />}
placement="bottom-start"
style="new"
visible={showDropdown}
onClickOutside={() => setShowDropdown(false)}
>
{sortedWorkspaces.map((workspace) => {
const isActive = workspace.uid === activeWorkspaceUid;
const isPinned = preferences?.workspaces?.pinnedWorkspaceUids?.includes(workspace.uid);
return (
<div
key={workspace.uid}
className={`dropdown-item workspace-item ${isActive ? 'active' : ''}`}
onClick={() => handleWorkspaceSwitch(workspace.uid)}
>
<span className="workspace-name">{toTitleCase(workspace.name)}</span>
<div className="workspace-actions">
{workspace.type !== 'default' && (
<button
className={`pin-btn ${isPinned ? 'pinned' : ''}`}
onClick={(e) => handlePinWorkspace(workspace.uid, e)}
title={isPinned ? 'Unpin workspace' : 'Pin workspace'}
>
{isPinned ? (
<IconPinned size={14} stroke={1.5} />
) : (
<IconPin size={14} stroke={1.5} />
)}
</button>
)}
{isActive && <IconCheck size={16} stroke={1.5} className="check-icon" />}
</div>
</div>
);
})}
<div className="label-item border-top">Workspaces</div>
<div className="dropdown-item" onClick={handleCreateWorkspace}>
<IconPlus size={16} stroke={1.5} className="icon" />
Create workspace
</div>
<div className="dropdown-item" onClick={handleOpenWorkspace}>
<IconFolder size={16} stroke={1.5} className="icon" />
Open workspace
</div>
</Dropdown>
</>
);
};
export default WorkspaceSelector;

View File

@@ -1,19 +1,18 @@
import { useState, forwardRef, useRef, useMemo, useCallback } from 'react';
import { useState, useRef } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import toast from 'react-hot-toast';
import { IconPlus, IconChevronDown, IconCheck, IconFolder, IconDownload, IconPin, IconPinned, IconHome, IconSearch, IconDeviceDesktop } from '@tabler/icons';
import { IconPlus, IconFolder, IconDownload, IconHome, IconSearch, IconDeviceDesktop } from '@tabler/icons';
import { showHomePage, savePreferences } from 'providers/ReduxStore/slices/app';
import { showHomePage } from 'providers/ReduxStore/slices/app';
import { openCollection, importCollection } from 'providers/ReduxStore/slices/collections/actions';
import { switchWorkspace, openWorkspaceDialog, importCollectionInWorkspace } from 'providers/ReduxStore/slices/workspaces/actions';
import { sortWorkspaces, toggleWorkspacePin } from 'utils/workspaces';
import { importCollectionInWorkspace } from 'providers/ReduxStore/slices/workspaces/actions';
import Dropdown from 'components/Dropdown';
import ImportCollection from 'components/Sidebar/ImportCollection';
import ImportCollectionLocation from 'components/Sidebar/ImportCollectionLocation';
import CreateWorkspace from 'components/WorkspaceSidebar/CreateWorkspace';
import CreateCollection from '../CreateCollection';
import WorkspaceSelector from './WorkspaceSelector';
import StyledWrapper from './StyledWrapper';
const TitleBar = ({ showSearch, setShowSearch }) => {
@@ -21,28 +20,15 @@ const TitleBar = ({ showSearch, setShowSearch }) => {
const { ipcRenderer } = window;
const { workspaces, activeWorkspaceUid } = useSelector((state) => state.workspaces);
const preferences = useSelector((state) => state.app.preferences);
const activeWorkspace = workspaces.find((w) => w.uid === activeWorkspaceUid);
// Sort workspaces according to preferences
const sortedWorkspaces = useMemo(() => {
return sortWorkspaces(workspaces, preferences);
}, [workspaces, preferences]);
const [importData, setImportData] = useState(null);
const [createCollectionModalOpen, setCreateCollectionModalOpen] = useState(false);
const [importCollectionModalOpen, setImportCollectionModalOpen] = useState(false);
const [importCollectionLocationModalOpen, setImportCollectionLocationModalOpen] = useState(false);
const [createWorkspaceModalOpen, setCreateWorkspaceModalOpen] = useState(false);
const toTitleCase = (str) => {
if (!str) return '';
if (str === 'default') return 'Default';
return str
.split(/[\s-_]+/)
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join(' ');
};
const actionsDropdownTippyRef = useRef();
const onActionsDropdownCreate = (ref) => (actionsDropdownTippyRef.current = ref);
const handleImportCollection = ({ rawData, type }) => {
setImportCollectionModalOpen(false);
@@ -71,56 +57,12 @@ const TitleBar = ({ showSearch, setShowSearch }) => {
});
};
const [showWorkspaceDropdown, setShowWorkspaceDropdown] = useState(false);
const workspaceDropdownTippyRef = useRef();
const onWorkspaceDropdownCreate = (ref) => (workspaceDropdownTippyRef.current = ref);
const actionsDropdownTippyRef = useRef();
const onActionsDropdownCreate = (ref) => (actionsDropdownTippyRef.current = ref);
const WorkspaceName = forwardRef((props, ref) => {
return (
<div ref={ref} className="workspace-name-container" onClick={() => setShowWorkspaceDropdown(!showWorkspaceDropdown)}>
<span className="workspace-name">{toTitleCase(activeWorkspace?.name) || 'Default Workspace'}</span>
<IconChevronDown size={14} stroke={1.5} className="chevron-icon" />
</div>
);
});
const handleToggleSearch = () => {
if (setShowSearch) {
setShowSearch((prev) => !prev);
}
};
const handleWorkspaceSwitch = (workspaceUid) => {
dispatch(switchWorkspace(workspaceUid));
setShowWorkspaceDropdown(false);
toast.success(`Switched to ${workspaces.find((w) => w.uid === workspaceUid)?.name}`);
};
const handleOpenWorkspace = async () => {
setShowWorkspaceDropdown(false);
try {
await dispatch(openWorkspaceDialog());
toast.success('Workspace opened successfully');
} catch (error) {
toast.error(error.message || 'Failed to open workspace');
}
};
const handleCreateWorkspace = () => {
setShowWorkspaceDropdown(false);
setCreateWorkspaceModalOpen(true);
};
const handlePinWorkspace = useCallback((workspaceUid, e) => {
e.preventDefault();
e.stopPropagation();
const newPreferences = toggleWorkspacePin(workspaceUid, preferences);
dispatch(savePreferences(newPreferences));
}, [dispatch, preferences]);
const handleOpenCollection = () => {
const options = {};
if (activeWorkspace?.pathname) {
@@ -141,9 +83,6 @@ const TitleBar = ({ showSearch, setShowSearch }) => {
{createCollectionModalOpen && (
<CreateCollection
onClose={() => setCreateCollectionModalOpen(false)}
workspaceUid={activeWorkspace?.uid}
defaultLocation={activeWorkspace?.type !== 'default' && activeWorkspace?.pathname ? `${activeWorkspace.pathname}/collections` : undefined}
hideLocationInput={activeWorkspace?.type !== 'default' && !!activeWorkspace?.pathname}
/>
)}
{importCollectionModalOpen && (
@@ -160,9 +99,6 @@ const TitleBar = ({ showSearch, setShowSearch }) => {
handleSubmit={handleImportCollectionLocation}
/>
)}
{createWorkspaceModalOpen && (
<CreateWorkspace onClose={() => setCreateWorkspaceModalOpen(false)} />
)}
</>
);
@@ -170,60 +106,8 @@ const TitleBar = ({ showSearch, setShowSearch }) => {
<StyledWrapper className="px-2 py-2">
{renderModals()}
<div className="titlebar-container">
<WorkspaceSelector />
{/* Workspace Dropdown */}
<Dropdown
onCreate={onWorkspaceDropdownCreate}
icon={<WorkspaceName />}
placement="bottom-start"
style="new"
visible={showWorkspaceDropdown}
onClickOutside={() => setShowWorkspaceDropdown(false)}
>
{sortedWorkspaces.map((workspace) => {
const isActive = workspace.uid === activeWorkspaceUid;
const isPinned = preferences?.workspaces?.pinnedWorkspaceUids?.includes(workspace.uid);
return (
<div
key={workspace.uid}
className={`dropdown-item workspace-item ${isActive ? 'active' : ''}`}
onClick={() => handleWorkspaceSwitch(workspace.uid)}
>
<span className="workspace-name">{toTitleCase(workspace.name)}</span>
<div className="workspace-actions">
{workspace.type !== 'default' && (
<button
className={`pin-btn ${isPinned ? 'pinned' : ''}`}
onClick={(e) => handlePinWorkspace(workspace.uid, e)}
title={isPinned ? 'Unpin workspace' : 'Pin workspace'}
>
{isPinned ? (
<IconPinned size={14} stroke={1.5} />
) : (
<IconPin size={14} stroke={1.5} />
)}
</button>
)}
{isActive && <IconCheck size={16} stroke={1.5} className="check-icon" />}
</div>
</div>
);
})}
<div className="label-item border-top">Workspaces</div>
<div className="dropdown-item" onClick={handleCreateWorkspace}>
<IconPlus size={16} stroke={1.5} className="icon" />
Create workspace
</div>
<div className="dropdown-item" onClick={handleOpenWorkspace}>
<IconFolder size={16} stroke={1.5} className="icon" />
Open workspace
</div>
</Dropdown>
{/* Search and Actions */}
<div className="actions-container">
<button className="home-icon-button" onClick={() => dispatch(showHomePage())} title="Home">
<IconHome size={16} stroke={1.5} />

View File

@@ -5,7 +5,7 @@ const StyledWrapper = styled.div`
display: flex;
flex-direction: column;
height: 100%;
font-size: 13px;
font-size: ${(props) => props.theme.font.size.base};
}
.collections-header {
@@ -28,7 +28,7 @@ const StyledWrapper = styled.div`
.header-cell {
font-weight: 600;
font-size: 11px;
font-size: ${(props) => props.theme.font.size.xs};
color: ${(props) => props.theme.text.muted};
text-transform: uppercase;
letter-spacing: 0.5px;
@@ -48,14 +48,7 @@ const StyledWrapper = styled.div`
border-bottom: ${(props) => props.theme.workspace.collection.item.indentBorder};
transition: background-color 0.15s ease;
cursor: pointer;
&:has(.cell-git) {
grid-template-columns: 1fr 3fr 1fr 1.5fr;
}
&:not(:has(.cell-git)) {
grid-template-columns: 1fr 3fr 1.5fr;
}
grid-template-columns: 1fr 3fr 1.5fr;
&:hover {
background-color: ${(props) => props.theme.sidebar.bg};
@@ -86,14 +79,14 @@ const StyledWrapper = styled.div`
.collection-name {
font-weight: 400;
color: ${(props) => props.theme.text};
font-size: 13px;
font-size: ${(props) => props.theme.font.size.base};
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.collection-subtitle {
font-size: 11px;
font-size: ${(props) => props.theme.font.size.xs};
color: ${(props) => props.theme.text.muted};
white-space: nowrap;
overflow: hidden;
@@ -102,36 +95,9 @@ const StyledWrapper = styled.div`
}
}
.cell-type {
.type-icon {
flex-shrink: 0;
&.remote {
color: #3B82F6;
}
&.local {
color: ${(props) => props.theme.workspace.accent};
}
}
.type-label {
font-size: 12px;
font-weight: 500;
&.remote {
color: #3B82F6;
}
&.local {
color: ${(props) => props.theme.text.primary};
}
}
}
.cell-location {
.location-text {
font-size: 12px;
font-size: ${(props) => props.theme.font.size.sm};
color: ${(props) => props.theme.text.muted};
white-space: nowrap;
overflow: hidden;
@@ -140,77 +106,6 @@ const StyledWrapper = styled.div`
}
}
.cell-git {
.git-badge {
display: flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
border-radius: 12px;
font-size: 11px;
font-weight: 500;
width: fit-content;
&.git-enabled {
background-color: #10B98120;
color: #10B981;
}
&.git-disabled {
background-color: ${(props) => props.theme.workspace.border}40;
color: ${(props) => props.theme.text.muted};
}
}
}
.cell-requests {
.request-count {
display: flex;
align-items: baseline;
gap: 4px;
}
.count-number {
font-weight: 600;
font-size: 14px;
color: ${(props) => props.theme.text.primary};
}
.count-label {
font-size: 11px;
color: ${(props) => props.theme.text.muted};
}
}
.cell-status {
.status-badge {
display: flex;
align-items: center;
gap: 4px;
padding: 3px 8px;
border-radius: 12px;
font-size: 11px;
font-weight: 500;
&.status-ready {
background-color: ${(props) => props.theme.workspace.accent}20;
color: ${(props) => props.theme.workspace.accent};
.status-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background-color: ${(props) => props.theme.workspace.accent};
}
}
&.status-not-loaded {
background-color: ${(props) => props.theme.workspace.border}40;
color: ${(props) => props.theme.text.muted};
}
}
}
.cell-actions {
justify-content: flex-end;
@@ -241,14 +136,6 @@ const StyledWrapper = styled.div`
&:hover:not(:disabled) {
background-color: ${(props) => props.theme.listItem.hoverBg};
&.action-git {
color: #F97316;
}
&.action-run {
color: #10B981;
}
&.action-edit {
color: ${(props) => props.theme.text};
}
@@ -257,10 +144,6 @@ const StyledWrapper = styled.div`
color: #3B82F6;
}
&.action-settings {
color: ${(props) => props.theme.text};
}
&.action-delete {
color: #EF4444;
}

View File

@@ -1,10 +1,9 @@
import React, { useState, useEffect } from 'react';
import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { IconBox, IconTrash, IconEdit, IconShare } from '@tabler/icons';
import { loadWorkspaceCollections, removeCollectionFromWorkspaceAction, importCollectionInWorkspace } from 'providers/ReduxStore/slices/workspaces/actions';
import { removeCollectionFromWorkspaceAction, importCollectionInWorkspace } from 'providers/ReduxStore/slices/workspaces/actions';
import { addTab } from 'providers/ReduxStore/slices/tabs';
import { hideHomePage } from 'providers/ReduxStore/slices/app';
import { uuid } from 'utils/common';
import toast from 'react-hot-toast';
import Modal from 'components/Modal';
import CreateCollection from 'components/Sidebar/CreateCollection';
@@ -25,12 +24,6 @@ const WorkspaceCollections = ({ workspace, onImportCollection }) => {
const [createCollectionModalOpen, setCreateCollectionModalOpen] = useState(false);
const [importCollectionModalOpen, setImportCollectionModalOpen] = useState(false);
useEffect(() => {
if (workspace && workspace.uid) {
dispatch(loadWorkspaceCollections(workspace.uid));
}
}, [workspace?.uid, collections.length, dispatch]);
const handleImportCollection = ({ rawData, type }) => {
if (onImportCollection) {
onImportCollection();
@@ -199,9 +192,6 @@ const WorkspaceCollections = ({ workspace, onImportCollection }) => {
{createCollectionModalOpen && (
<CreateCollection
onClose={() => setCreateCollectionModalOpen(false)}
workspaceUid={workspace.uid}
defaultLocation={`${workspace.pathname}/collections`}
hideLocationInput={true}
/>
)}

View File

@@ -1,7 +1,7 @@
import React, { useEffect, useState, useRef } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { IconCategory, IconPlus, IconFolders, IconFileImport, IconDots, IconEdit, IconX, IconCheck, IconFolder } from '@tabler/icons';
import { loadWorkspaceCollections, importCollectionInWorkspace, renameWorkspaceAction } from 'providers/ReduxStore/slices/workspaces/actions';
import { importCollectionInWorkspace, renameWorkspaceAction } from 'providers/ReduxStore/slices/workspaces/actions';
import { showInFolder, openCollection } from 'providers/ReduxStore/slices/collections/actions';
import toast from 'react-hot-toast';
import CreateCollection from 'components/Sidebar/CreateCollection';
@@ -13,7 +13,7 @@ import WorkspaceEnvironments from './WorkspaceEnvironments';
import StyledWrapper from './StyledWrapper';
import Dropdown from 'components/Dropdown';
const WorkspaceOverview = () => {
const WorkspaceHome = () => {
const dispatch = useDispatch();
const { workspaces, activeWorkspaceUid } = useSelector((state) => state.workspaces);
const [activeTab, setActiveTab] = useState('collections');
@@ -32,12 +32,6 @@ const WorkspaceOverview = () => {
const activeWorkspace = workspaces.find((w) => w.uid === activeWorkspaceUid);
useEffect(() => {
if (activeWorkspaceUid && activeWorkspace) {
dispatch(loadWorkspaceCollections(activeWorkspaceUid));
}
}, [activeWorkspaceUid, dispatch, activeWorkspace]);
useEffect(() => {
if (!isRenamingWorkspace) return;
@@ -214,9 +208,6 @@ const WorkspaceOverview = () => {
{createCollectionModalOpen && (
<CreateCollection
onClose={() => setCreateCollectionModalOpen(false)}
workspaceUid={activeWorkspace.uid}
defaultLocation={`${activeWorkspace.pathname}/collections`}
hideLocationInput={true}
/>
)}
@@ -357,4 +348,4 @@ const WorkspaceOverview = () => {
);
};
export default WorkspaceOverview;
export default WorkspaceHome;

View File

@@ -1,6 +1,6 @@
import React, { useState, useRef, useEffect } from 'react';
import classnames from 'classnames';
import WorkspaceOverview from 'components/WorkspaceOverview';
import WorkspaceHome from 'components/WorkspaceHome';
import RequestTabs from 'components/RequestTabs';
import RequestTabPanel from 'components/RequestTabPanel';
import Sidebar from 'components/Sidebar';
@@ -112,7 +112,7 @@ export default function Main() {
<Sidebar />
<section className="flex flex-grow flex-col overflow-hidden">
{showHomePage ? (
<WorkspaceOverview />
<WorkspaceHome />
) : (
<>
<RequestTabs />

View File

@@ -22,8 +22,7 @@ import {
streamDataReceived
} from 'providers/ReduxStore/slices/collections';
import { collectionAddEnvFileEvent, openCollectionEvent, hydrateCollectionWithUiStateSnapshot, mergeAndPersistEnvironment } from 'providers/ReduxStore/slices/collections/actions';
import { workspaceOpenedEvent, workspaceConfigUpdatedEvent, loadLastOpenedWorkspaces, switchWorkspace, loadWorkspaceCollections } from 'providers/ReduxStore/slices/workspaces/actions';
import { createWorkspace } from 'providers/ReduxStore/slices/workspaces';
import { workspaceOpenedEvent, workspaceConfigUpdatedEvent } from 'providers/ReduxStore/slices/workspaces/actions';
import toast from 'react-hot-toast';
import { useDispatch } from 'react-redux';
import { isElectron } from 'utils/common/platform';
@@ -92,33 +91,6 @@ const useIpcEvents = () => {
}
};
const initializeDefaultWorkspace = async () => {
try {
const defaultWorkspace = await ipcRenderer.invoke('renderer:get-default-workspace');
if (defaultWorkspace) {
const { workspaceConfig, workspaceUid, workspacePath } = defaultWorkspace;
dispatch(createWorkspace({
uid: workspaceUid,
name: workspaceConfig.name || 'Default',
type: 'default',
pathname: workspacePath,
collections: [],
docs: workspaceConfig.docs || ''
}));
await dispatch(loadWorkspaceCollections(workspaceUid));
dispatch(switchWorkspace(workspaceUid));
}
} catch (error) {
console.error('Error loading default workspace:', error);
}
};
initializeDefaultWorkspace();
dispatch(loadLastOpenedWorkspaces());
ipcRenderer.invoke('renderer:ready');
const removeCollectionTreeUpdateListener = ipcRenderer.on('main:collection-tree-updated', _collectionTreeUpdated);

View File

@@ -58,8 +58,7 @@ import {
import { each } from 'lodash';
import { closeAllCollectionTabs, updateResponsePaneScrollPosition } from 'providers/ReduxStore/slices/tabs';
import { addCollectionToWorkspace, removeCollectionFromWorkspace } from 'providers/ReduxStore/slices/workspaces';
import { loadWorkspaceCollections } from 'providers/ReduxStore/slices/workspaces/actions';
import { removeCollectionFromWorkspace } from 'providers/ReduxStore/slices/workspaces';
import { resolveRequestFilename } from 'utils/common/platform';
import { interpolateUrl, parsePathParams, splitOnFirst } from 'utils/url/index';
import { sendCollectionOauth2Request as _sendCollectionOauth2Request } from 'utils/network/index';
@@ -2252,21 +2251,14 @@ export const openCollectionEvent = (uid, pathname, brunoConfig) => (dispatch, ge
path: pathname
};
// The electron handler will automatically trigger workspace config update
// which will cause the app to react and reload collections
ipcRenderer
.invoke('renderer:add-collection-to-workspace', activeWorkspace.pathname, workspaceCollection)
.then(() => {
dispatch(addCollectionToWorkspace({
workspaceUid: activeWorkspace.uid,
collection: workspaceCollection
}));
dispatch(loadWorkspaceCollections(activeWorkspace.uid, true));
})
.catch((err) => {
console.error('Failed to add collection to workspace', err);
toast.error('Failed to add collection to workspace');
});
} else {
dispatch(loadWorkspaceCollections(activeWorkspace.uid, true));
}
}
@@ -2382,11 +2374,6 @@ export const importCollection = (collection, collectionLocation, options = {}) =
};
await ipcRenderer.invoke('renderer:add-collection-to-workspace', activeWorkspace.pathname, workspaceCollection);
dispatch(addCollectionToWorkspace({
workspaceUid: activeWorkspace.uid,
collection: workspaceCollection
}));
}
resolve(collectionPath);

View File

@@ -301,7 +301,7 @@ export const loadLastOpenedWorkspaces = () => {
};
export const workspaceOpenedEvent = (workspacePath, workspaceUid, workspaceConfig) => {
return async (dispatch) => {
return async (dispatch, getState) => {
dispatch(createWorkspace({
uid: workspaceUid,
pathname: workspacePath,
@@ -312,6 +312,14 @@ export const workspaceOpenedEvent = (workspacePath, workspaceUid, workspaceConfi
await dispatch(loadWorkspaceCollections(workspaceUid));
} catch (error) {
}
// If this is the default workspace or no workspace is active yet, switch to it
const state = getState();
const activeWorkspaceUid = state.workspaces.activeWorkspaceUid;
if (!activeWorkspaceUid || workspaceConfig.type === 'default') {
dispatch(switchWorkspace(workspaceUid));
}
};
};
@@ -478,11 +486,6 @@ export const importCollectionInWorkspace = (collection, workspaceUid, collection
await ipcRenderer.invoke('renderer:add-collection-to-workspace', currentWorkspace.pathname, workspaceCollection);
dispatch(addCollectionToWorkspace({
workspaceUid,
collection: workspaceCollection
}));
return collectionPath;
};
};

View File

@@ -24,6 +24,8 @@ const registerPreferencesIpc = (mainWindow, watcher) => {
console.error("Error occured while fetching global environements!");
console.error(error);
}
ipcMain.emit('main:renderer-ready', mainWindow);
});
ipcMain.on('main:open-preferences', () => {

View File

@@ -318,7 +318,13 @@ const registerWorkspaceIpc = (mainWindow, workspaceWatcher) => {
ipcMain.handle('renderer:add-collection-to-workspace', async (event, workspacePath, collection) => {
try {
const normalizedCollection = normalizeCollectionEntry(workspacePath, collection);
return await addCollectionToWorkspace(workspacePath, normalizedCollection);
const updatedCollections = await addCollectionToWorkspace(workspacePath, normalizedCollection);
const workspaceConfig = readWorkspaceConfig(workspacePath);
const workspaceUid = generateUidBasedOnHash(workspacePath);
mainWindow.webContents.send('main:workspace-config-updated', workspacePath, workspaceUid, workspaceConfig);
return updatedCollections;
} catch (error) {
throw error;
}
@@ -431,6 +437,66 @@ const registerWorkspaceIpc = (mainWindow, workspaceWatcher) => {
return null;
}
});
ipcMain.on('main:renderer-ready', async (win) => {
try {
const defaultResult = await defaultWorkspaceManager.ensureDefaultWorkspaceExists();
if (defaultResult) {
const { workspacePath, workspaceUid } = defaultResult;
const workspaceFilePath = path.join(workspacePath, 'workspace.yml');
if (fs.existsSync(workspaceFilePath)) {
const yamlContent = fs.readFileSync(workspaceFilePath, 'utf8');
const workspaceConfig = yaml.load(yamlContent);
win.webContents.send('main:workspace-opened', workspacePath, workspaceUid, {
...workspaceConfig,
type: 'default'
});
if (workspaceWatcher) {
workspaceWatcher.addWatcher(win, workspacePath);
}
}
}
const workspaces = lastOpenedWorkspaces.getAll();
const invalidWorkspaceUids = [];
for (const workspace of workspaces) {
if (workspace.pathname) {
const workspaceYmlPath = path.join(workspace.pathname, 'workspace.yml');
if (fs.existsSync(workspaceYmlPath)) {
try {
const workspaceConfig = readWorkspaceConfig(workspace.pathname);
validateWorkspaceConfig(workspaceConfig);
const workspaceUid = generateUidBasedOnHash(workspace.pathname);
win.webContents.send('main:workspace-opened', workspace.pathname, workspaceUid, workspaceConfig);
if (workspaceWatcher) {
workspaceWatcher.addWatcher(win, workspace.pathname);
}
} catch (error) {
console.error(`Error loading workspace ${workspace.pathname}:`, error);
invalidWorkspaceUids.push(workspace.uid);
}
} else {
invalidWorkspaceUids.push(workspace.uid);
}
} else {
invalidWorkspaceUids.push(workspace.uid);
}
}
for (const uid of invalidWorkspaceUids) {
lastOpenedWorkspaces.remove(uid);
}
} catch (error) {
console.error('Error initializing workspaces:', error);
}
});
};
module.exports = registerWorkspaceIpc;