mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-24 13:15:40 +00:00
fix: collection already opened in other workspace (#6574)
* fix: collection already opened in other workspace * fix * fixes
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
Reference in New Issue
Block a user