fix: collection already opened in other workspace (#6574)

* fix: collection already opened in other workspace

* fix

* fixes
This commit is contained in:
naman-bruno
2026-01-01 14:30:26 +05:30
committed by GitHub
parent 1ec8f55a9e
commit 8e74fa6233
7 changed files with 165 additions and 49 deletions

View File

@@ -177,10 +177,6 @@ const useIpcEvents = () => {
}
});
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') {
return toast.error(error || 'Something went wrong!');
@@ -290,7 +286,6 @@ const useIpcEvents = () => {
removeWorkspaceEnvironmentAddedListener();
removeWorkspaceEnvironmentChangedListener();
removeWorkspaceEnvironmentDeletedListener();
removeCollectionAlreadyOpenedListener();
removeDisplayErrorListener();
removeScriptEnvUpdateListener();
removeGlobalEnvironmentVariablesUpdateListener();

View File

@@ -2242,43 +2242,94 @@ export const updateBrunoConfig = (brunoConfig, collectionUid) => (dispatch, getS
};
export const openCollectionEvent = (uid, pathname, brunoConfig) => (dispatch, getState) => {
const collection = {
version: '1',
uid: uid,
name: brunoConfig.name,
pathname: pathname,
items: [],
runtimeVariables: {},
brunoConfig: brunoConfig
};
const { ipcRenderer } = window;
return new Promise((resolve, reject) => {
const state = getState();
const activeWorkspace = state.workspaces.workspaces.find((w) => w.uid === state.workspaces.activeWorkspaceUid);
// Check if collection already exists in Redux state
const existingCollection = state.collections.collections.find(
(c) => normalizePath(c.pathname) === normalizePath(pathname)
);
// Check if collection is already in the current workspace
const isAlreadyInWorkspace = activeWorkspace?.collections?.some(
(c) => normalizePath(c.path) === normalizePath(pathname)
);
// If collection already exists in Redux AND in current workspace, show toast and return
if (existingCollection && isAlreadyInWorkspace) {
toast.success('Collection is already opened');
resolve();
return;
}
// If collection exists in Redux but not in workspace, add to workspace
if (existingCollection) {
if (state.app.sidebarCollapsed) {
dispatch(toggleSidebarCollapse());
}
if (activeWorkspace) {
const workspaceCollection = {
name: brunoConfig.name,
path: pathname
};
ipcRenderer
.invoke('renderer:add-collection-to-workspace', activeWorkspace.pathname, workspaceCollection)
.then(() => {
toast.success('Collection added to workspace');
})
.catch((err) => {
console.error('Failed to add collection to workspace', err);
toast.error('Failed to add collection to workspace');
});
}
resolve();
return;
}
// Collection doesn't exist - create it
const collection = {
version: '1',
uid: uid,
name: brunoConfig.name,
pathname: pathname,
items: [],
runtimeVariables: {},
brunoConfig: brunoConfig
};
ipcRenderer.invoke('renderer:get-collection-security-config', pathname).then((securityConfig) => {
collectionSchema
.validate(collection)
.then(() => dispatch(_createCollection({ ...collection, securityConfig })))
.then(() => {
const state = getState();
if (state.app.sidebarCollapsed) {
const currentState = getState();
if (currentState.app.sidebarCollapsed) {
dispatch(toggleSidebarCollapse());
}
const activeWorkspace = state.workspaces.workspaces.find((w) => w.uid === state.workspaces.activeWorkspaceUid);
if (activeWorkspace) {
const isAlreadyInWorkspace = activeWorkspace.collections?.some(
const currentWorkspace = currentState.workspaces.workspaces.find(
(w) => w.uid === currentState.workspaces.activeWorkspaceUid
);
if (currentWorkspace) {
const alreadyInWorkspace = currentWorkspace.collections?.some(
(c) => normalizePath(c.path) === normalizePath(pathname)
);
if (!isAlreadyInWorkspace) {
if (!alreadyInWorkspace) {
const workspaceCollection = {
name: brunoConfig.name,
path: pathname
};
ipcRenderer
.invoke('renderer:add-collection-to-workspace', activeWorkspace.pathname, workspaceCollection)
.invoke('renderer:add-collection-to-workspace', currentWorkspace.pathname, workspaceCollection)
.catch((err) => {
console.error('Failed to add collection to workspace', err);
toast.error('Failed to add collection to workspace');

View File

@@ -184,8 +184,12 @@ const loadWorkspaceCollectionsForSwitch = async (dispatch, workspace) => {
.map((wc) => wc.path)
.filter((p) => p && !alreadyOpenCollections.includes(normalizePath(p)));
if (collectionPaths.length > 0) {
await openCollectionsFunction(collectionPaths, updatedWorkspace.pathname);
const uniqueCollectionPaths = [...new Map(
collectionPaths.map((p) => [normalizePath(p), p])
).values()];
if (uniqueCollectionPaths.length > 0) {
await openCollectionsFunction(uniqueCollectionPaths, updatedWorkspace.pathname);
}
}
@@ -405,9 +409,14 @@ export const workspaceConfigUpdatedEvent = (workspacePath, workspaceUid, workspa
.map((workspaceCollection) => workspaceCollection.path)
.filter((collectionPath) => collectionPath && !openCollections.includes(normalizePath(collectionPath)));
if (newCollectionPaths.length > 0) {
// Deduplicate paths to prevent "collection already opened" toast
const uniqueNewCollectionPaths = [...new Map(
newCollectionPaths.map((p) => [normalizePath(p), p])
).values()];
if (uniqueNewCollectionPaths.length > 0) {
try {
await dispatch(openMultipleCollections(newCollectionPaths, { workspaceId: workspace.pathname }));
await dispatch(openMultipleCollections(uniqueNewCollectionPaths, { workspaceId: workspace.pathname }));
} catch (error) {
}
}

View File

@@ -98,26 +98,17 @@ const openCollectionDialog = async (win, watcher) => {
};
const openCollection = async (win, watcher, collectionPath, options = {}) => {
if (!watcher.hasWatcher(collectionPath)) {
// If watcher already exists, collection is already loaded in the app
// Just send the collection info so frontend can add to workspace if needed
if (watcher.hasWatcher(collectionPath)) {
try {
let brunoConfig = await getCollectionConfigFile(collectionPath);
const uid = generateUidBasedOnHash(collectionPath);
// Always ensure node_modules and .git are ignored, regardless of user config
// This prevents infinite loops with symlinked directories (e.g., npm workspaces)
const defaultIgnores = ['node_modules', '.git'];
const userIgnores = brunoConfig.ignore || [];
brunoConfig.ignore = [...new Set([...defaultIgnores, ...userIgnores])];
// Transform the config to add existence checks for protobuf files and import paths
brunoConfig = await transformBrunoConfigAfterRead(brunoConfig, collectionPath);
const { size, filesCount } = await getCollectionStats(collectionPath);
brunoConfig.size = size;
brunoConfig.filesCount = filesCount;
win.webContents.send('main:collection-opened', collectionPath, uid, brunoConfig);
ipcMain.emit('main:collection-opened', win, collectionPath, uid, brunoConfig);
} catch (err) {
if (!options.dontSendDisplayErrors) {
win.webContents.send('main:display-error', {
@@ -125,16 +116,49 @@ const openCollection = async (win, watcher, collectionPath, options = {}) => {
});
}
}
} else {
win.webContents.send('main:collection-already-opened', collectionPath);
return;
}
try {
let brunoConfig = await getCollectionConfigFile(collectionPath);
const uid = generateUidBasedOnHash(collectionPath);
// Always ensure node_modules and .git are ignored, regardless of user config
const defaultIgnores = ['node_modules', '.git'];
const userIgnores = brunoConfig.ignore || [];
brunoConfig.ignore = [...new Set([...defaultIgnores, ...userIgnores])];
brunoConfig = await transformBrunoConfigAfterRead(brunoConfig, collectionPath);
const { size, filesCount } = await getCollectionStats(collectionPath);
brunoConfig.size = size;
brunoConfig.filesCount = filesCount;
win.webContents.send('main:collection-opened', collectionPath, uid, brunoConfig);
ipcMain.emit('main:collection-opened', win, collectionPath, uid, brunoConfig);
} catch (err) {
if (!options.dontSendDisplayErrors) {
win.webContents.send('main:display-error', {
message: err.message || 'An error occurred while opening the local collection'
});
}
}
};
const openCollectionsByPathname = async (win, watcher, collectionPaths, options = {}) => {
const seenPaths = new Set();
for (const collectionPath of collectionPaths) {
const resolvedPath = path.isAbsolute(collectionPath)
? collectionPath
: normalizeAndResolvePath(collectionPath);
const normalizedPath = path.normalize(resolvedPath);
if (seenPaths.has(normalizedPath)) {
continue;
}
seenPaths.add(normalizedPath);
if (isDirectory(resolvedPath)) {
await openCollection(win, watcher, resolvedPath, options);
} else {

View File

@@ -4,6 +4,7 @@ const fsExtra = require('fs-extra');
const archiver = require('archiver');
const extractZip = require('extract-zip');
const { ipcMain, dialog } = require('electron');
const isDev = require('electron-is-dev');
const { createDirectory, sanitizeName } = require('../utils/filesystem');
const yaml = require('js-yaml');
const LastOpenedWorkspaces = require('../store/last-opened-workspaces');
@@ -609,11 +610,22 @@ const registerWorkspaceIpc = (mainWindow, workspaceWatcher) => {
}
});
// Guard to prevent main:renderer-ready from running multiple times (only needed in dev mode due to strict mode)
let rendererReadyProcessed = false;
ipcMain.on('main:renderer-ready', async (win) => {
if (isDev && rendererReadyProcessed) {
return;
}
rendererReadyProcessed = true;
try {
let defaultWorkspacePath = null;
const defaultResult = await defaultWorkspaceManager.ensureDefaultWorkspaceExists();
if (defaultResult) {
const { workspacePath, workspaceUid } = defaultResult;
defaultWorkspacePath = workspacePath;
const workspaceConfig = readWorkspaceConfig(workspacePath);
const configForClient = prepareWorkspaceConfigForClient(workspaceConfig, true);
@@ -628,6 +640,10 @@ const registerWorkspaceIpc = (mainWindow, workspaceWatcher) => {
const invalidPaths = [];
for (const workspacePath of workspacePaths) {
if (defaultWorkspacePath && workspacePath === defaultWorkspacePath) {
continue;
}
const workspaceYmlPath = path.join(workspacePath, 'workspace.yml');
if (fs.existsSync(workspaceYmlPath)) {

View File

@@ -164,12 +164,20 @@ class DefaultWorkspaceManager {
const lastOpenedCollections = preferencesStore.get('lastOpenedCollections', []);
if (lastOpenedCollections && lastOpenedCollections.length > 0) {
const seenPaths = new Set();
const collections = lastOpenedCollections
.map((collectionPath) => {
if (!collectionPath || typeof collectionPath !== 'string') {
return null;
}
const absolutePath = path.resolve(collectionPath);
const normalizedPath = path.normalize(absolutePath);
if (seenPaths.has(normalizedPath)) {
return null;
}
seenPaths.add(normalizedPath);
const collectionName = path.basename(absolutePath);
return {

View File

@@ -400,15 +400,28 @@ const getWorkspaceCollections = (workspacePath) => {
const config = readWorkspaceConfig(workspacePath);
const collections = config.collections || [];
return collections.map((collection) => {
if (collection.path && !path.isAbsolute(collection.path)) {
return {
...collection,
path: path.resolve(workspacePath, collection.path)
};
}
return collection;
});
const seenPaths = new Set();
return collections
.map((collection) => {
if (collection.path && !path.isAbsolute(collection.path)) {
return {
...collection,
path: path.resolve(workspacePath, collection.path)
};
}
return collection;
})
.filter((collection) => {
if (!collection.path) {
return false;
}
const normalizedPath = path.normalize(collection.path);
if (seenPaths.has(normalizedPath)) {
return false;
}
seenPaths.add(normalizedPath);
return true;
});
};
const getWorkspaceApiSpecs = (workspacePath) => {