forked from extern/bruno
feat: local collections environment sync
This commit is contained in:
parent
ff87586a1d
commit
e98f219448
@ -8,7 +8,10 @@ import {
|
|||||||
localCollectionUnlinkDirectoryEvent
|
localCollectionUnlinkDirectoryEvent
|
||||||
} from 'providers/ReduxStore/slices/collections';
|
} from 'providers/ReduxStore/slices/collections';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import { openLocalCollectionEvent } from 'providers/ReduxStore/slices/collections/actions';
|
import {
|
||||||
|
openLocalCollectionEvent,
|
||||||
|
localCollectionLoadEnvironmentsEvent
|
||||||
|
} from 'providers/ReduxStore/slices/collections/actions';
|
||||||
import { isElectron } from 'utils/common/platform';
|
import { isElectron } from 'utils/common/platform';
|
||||||
|
|
||||||
const useLocalCollectionTreeSync = () => {
|
const useLocalCollectionTreeSync = () => {
|
||||||
@ -54,6 +57,12 @@ const useLocalCollectionTreeSync = () => {
|
|||||||
directory: val
|
directory: val
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
if(type === 'addEnvironmentFile') {
|
||||||
|
dispatch(localCollectionLoadEnvironmentsEvent(val));
|
||||||
|
}
|
||||||
|
if(type === 'changeEnvironmentFile') {
|
||||||
|
dispatch(localCollectionLoadEnvironmentsEvent(val));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const _collectionAlreadyOpened = (pathname) => {
|
const _collectionAlreadyOpened = (pathname) => {
|
||||||
|
@ -18,7 +18,7 @@ import {
|
|||||||
refreshUidsInItem,
|
refreshUidsInItem,
|
||||||
interpolateEnvironmentVars
|
interpolateEnvironmentVars
|
||||||
} from 'utils/collections';
|
} from 'utils/collections';
|
||||||
import { collectionSchema, itemSchema } from '@usebruno/schema';
|
import { collectionSchema, itemSchema, environmentsSchema } from '@usebruno/schema';
|
||||||
import { waitForNextTick } from 'utils/common';
|
import { waitForNextTick } from 'utils/common';
|
||||||
import { getCollectionsFromIdb, saveCollectionToIdb, deleteCollectionInIdb } from 'utils/idb';
|
import { getCollectionsFromIdb, saveCollectionToIdb, deleteCollectionInIdb } from 'utils/idb';
|
||||||
import { sendNetworkRequest, cancelNetworkRequest } from 'utils/network';
|
import { sendNetworkRequest, cancelNetworkRequest } from 'utils/network';
|
||||||
@ -41,6 +41,7 @@ import {
|
|||||||
createCollection as _createCollection,
|
createCollection as _createCollection,
|
||||||
renameCollection as _renameCollection,
|
renameCollection as _renameCollection,
|
||||||
deleteCollection as _deleteCollection,
|
deleteCollection as _deleteCollection,
|
||||||
|
localCollectionLoadEnvironmentsEvent as _localCollectionLoadEnvironmentsEvent
|
||||||
} from './index';
|
} from './index';
|
||||||
|
|
||||||
import { closeTabs, addTab } from 'providers/ReduxStore/slices/tabs';
|
import { closeTabs, addTab } from 'providers/ReduxStore/slices/tabs';
|
||||||
@ -57,24 +58,6 @@ export const loadCollectionsFromIdb = () => (dispatch) => {
|
|||||||
.catch(() => toast.error("Error occured while loading collections from IndexedDB"));
|
.catch(() => toast.error("Error occured while loading collections from IndexedDB"));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const openLocalCollectionEvent = (uid, pathname, name) => (dispatch, getState) => {
|
|
||||||
const localCollection = {
|
|
||||||
version: "1",
|
|
||||||
uid: uid,
|
|
||||||
name: name,
|
|
||||||
pathname: pathname,
|
|
||||||
items: []
|
|
||||||
};
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
collectionSchema
|
|
||||||
.validate(localCollection)
|
|
||||||
.then(() => dispatch(_createCollection(localCollection)))
|
|
||||||
.then(resolve)
|
|
||||||
.catch(reject);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createCollection = (collectionName) => (dispatch, getState) => {
|
export const createCollection = (collectionName) => (dispatch, getState) => {
|
||||||
const newCollection = {
|
const newCollection = {
|
||||||
version: "1",
|
version: "1",
|
||||||
@ -659,6 +642,15 @@ export const addEnvironment = (name, collectionUid) => (dispatch, getState) =>
|
|||||||
collectionToSave.environments = collectionToSave.environments || [];
|
collectionToSave.environments = collectionToSave.environments || [];
|
||||||
collectionToSave.environments.push(environment);
|
collectionToSave.environments.push(environment);
|
||||||
|
|
||||||
|
if(isLocalCollection(collection)) {
|
||||||
|
environmentsSchema
|
||||||
|
.validate(collectionToSave.environments)
|
||||||
|
.then(() => ipcRenderer.invoke('renderer:save-environment', collection.pathname, collectionToSave.environments))
|
||||||
|
.then(resolve)
|
||||||
|
.catch(reject);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
collectionSchema
|
collectionSchema
|
||||||
.validate(collectionToSave)
|
.validate(collectionToSave)
|
||||||
.then(() => saveCollectionToIdb(window.__idb, collectionToSave))
|
.then(() => saveCollectionToIdb(window.__idb, collectionToSave))
|
||||||
@ -683,9 +675,18 @@ export const renameEnvironment = (newName, environmentUid, collectionUid) => (d
|
|||||||
}
|
}
|
||||||
|
|
||||||
environment.name = newName;
|
environment.name = newName;
|
||||||
|
|
||||||
const collectionToSave = transformCollectionToSaveToIdb(collectionCopy);
|
const collectionToSave = transformCollectionToSaveToIdb(collectionCopy);
|
||||||
|
|
||||||
|
if(isLocalCollection(collection)) {
|
||||||
|
const environments = collectionToSave.environments;
|
||||||
|
environmentsSchema
|
||||||
|
.validate(environments)
|
||||||
|
.then(() => ipcRenderer.invoke('renderer:save-environment', collection.pathname, environments))
|
||||||
|
.then(resolve)
|
||||||
|
.catch(reject);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
collectionSchema
|
collectionSchema
|
||||||
.validate(collectionToSave)
|
.validate(collectionToSave)
|
||||||
.then(() => saveCollectionToIdb(window.__idb, collectionToSave))
|
.then(() => saveCollectionToIdb(window.__idb, collectionToSave))
|
||||||
@ -704,15 +705,25 @@ export const deleteEnvironment = (environmentUid, collectionUid) => (dispatch,
|
|||||||
}
|
}
|
||||||
|
|
||||||
const collectionCopy = cloneDeep(collection);
|
const collectionCopy = cloneDeep(collection);
|
||||||
|
|
||||||
const environment = findEnvironmentInCollection(collectionCopy, environmentUid);
|
const environment = findEnvironmentInCollection(collectionCopy, environmentUid);
|
||||||
if(!environment) {
|
if(!environment) {
|
||||||
return reject(new Error('Environment not found'));
|
return reject(new Error('Environment not found'));
|
||||||
}
|
}
|
||||||
|
|
||||||
collectionCopy.environments = filter(collectionCopy.environments, (e) => e.uid !== environmentUid);
|
collectionCopy.environments = filter(collectionCopy.environments, (e) => e.uid !== environmentUid);
|
||||||
|
|
||||||
const collectionToSave = transformCollectionToSaveToIdb(collectionCopy);
|
const collectionToSave = transformCollectionToSaveToIdb(collectionCopy);
|
||||||
|
|
||||||
|
if(isLocalCollection(collection)) {
|
||||||
|
const environments = collectionToSave.environments;
|
||||||
|
environmentsSchema
|
||||||
|
.validate(environments)
|
||||||
|
.then(() => ipcRenderer.invoke('renderer:save-environment', collection.pathname, environments))
|
||||||
|
.then(resolve)
|
||||||
|
.catch(reject);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
collectionSchema
|
collectionSchema
|
||||||
.validate(collectionToSave)
|
.validate(collectionToSave)
|
||||||
.then(() => saveCollectionToIdb(window.__idb, collectionToSave))
|
.then(() => saveCollectionToIdb(window.__idb, collectionToSave))
|
||||||
@ -739,6 +750,15 @@ export const saveEnvironment = (variables, environmentUid, collectionUid) => (di
|
|||||||
environment.variables = variables;
|
environment.variables = variables;
|
||||||
|
|
||||||
const collectionToSave = transformCollectionToSaveToIdb(collectionCopy);
|
const collectionToSave = transformCollectionToSaveToIdb(collectionCopy);
|
||||||
|
if(isLocalCollection(collection)) {
|
||||||
|
const environments = collectionToSave.environments;
|
||||||
|
environmentsSchema
|
||||||
|
.validate(environments)
|
||||||
|
.then(() => ipcRenderer.invoke('renderer:save-environment', collection.pathname, environments))
|
||||||
|
.then(resolve)
|
||||||
|
.catch(reject);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
collectionSchema
|
collectionSchema
|
||||||
.validate(collectionToSave)
|
.validate(collectionToSave)
|
||||||
@ -814,6 +834,24 @@ export const browserLocalDirectory = () => (dispatch, getState) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const openLocalCollectionEvent = (uid, pathname, name) => (dispatch, getState) => {
|
||||||
|
const localCollection = {
|
||||||
|
version: "1",
|
||||||
|
uid: uid,
|
||||||
|
name: name,
|
||||||
|
pathname: pathname,
|
||||||
|
items: []
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
collectionSchema
|
||||||
|
.validate(localCollection)
|
||||||
|
.then(() => dispatch(_createCollection(localCollection)))
|
||||||
|
.then(resolve)
|
||||||
|
.catch(reject);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const createLocalCollection = (collectionName, collectionLocation) => () => {
|
export const createLocalCollection = (collectionName, collectionLocation) => () => {
|
||||||
const { ipcRenderer } = window;
|
const { ipcRenderer } = window;
|
||||||
|
|
||||||
@ -835,3 +873,24 @@ export const openLocalCollection = () => () => {
|
|||||||
.catch(reject);
|
.catch(reject);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const localCollectionLoadEnvironmentsEvent = (payload) => (dispatch, getState) => {
|
||||||
|
const { data: environments, meta } = payload;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const state = getState();
|
||||||
|
const collection = findCollectionByUid(state.collections.collections, meta.collectionUid);
|
||||||
|
if(!collection) {
|
||||||
|
return reject(new Error('Collection not found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
environmentsSchema
|
||||||
|
.validate(environments)
|
||||||
|
.then(() => dispatch(_localCollectionLoadEnvironmentsEvent({
|
||||||
|
environments,
|
||||||
|
collectionUid: meta.collectionUid
|
||||||
|
})))
|
||||||
|
.then(resolve)
|
||||||
|
.catch(reject);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
@ -742,7 +742,15 @@ export const collectionsSlice = createSlice({
|
|||||||
deleteItemInCollection(item.uid, collection);
|
deleteItemInCollection(item.uid, collection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
localCollectionLoadEnvironmentsEvent: (state, action) => {
|
||||||
|
const { environments, collectionUid } = action.payload;
|
||||||
|
const collection = findCollectionByUid(state.collections, collectionUid);
|
||||||
|
|
||||||
|
if(collection) {
|
||||||
|
collection.environments = environments;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -788,7 +796,8 @@ export const {
|
|||||||
localCollectionAddDirectoryEvent,
|
localCollectionAddDirectoryEvent,
|
||||||
localCollectionChangeFileEvent,
|
localCollectionChangeFileEvent,
|
||||||
localCollectionUnlinkFileEvent,
|
localCollectionUnlinkFileEvent,
|
||||||
localCollectionUnlinkDirectoryEvent
|
localCollectionUnlinkDirectoryEvent,
|
||||||
|
localCollectionLoadEnvironmentsEvent
|
||||||
} = collectionsSlice.actions;
|
} = collectionsSlice.actions;
|
||||||
|
|
||||||
export default collectionsSlice.reducer;
|
export default collectionsSlice.reducer;
|
||||||
|
@ -3,11 +3,75 @@ const path = require('path');
|
|||||||
const chokidar = require('chokidar');
|
const chokidar = require('chokidar');
|
||||||
const { hasJsonExtension } = require('../utils/filesystem');
|
const { hasJsonExtension } = require('../utils/filesystem');
|
||||||
|
|
||||||
const add = async (win, pathname, collectionUid) => {
|
const isEnvironmentConfig = (pathname, collectionPath) => {
|
||||||
|
const dirname = path.dirname(pathname);
|
||||||
|
const basename = path.basename(pathname);
|
||||||
|
|
||||||
|
return dirname === collectionPath && basename === 'environments.json';
|
||||||
|
}
|
||||||
|
|
||||||
|
const addEnvironmentFile = async (win, pathname, collectionUid) => {
|
||||||
|
try {
|
||||||
|
const file = {
|
||||||
|
meta: {
|
||||||
|
collectionUid,
|
||||||
|
pathname,
|
||||||
|
name: path.basename(pathname),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const jsonData = fs.readFileSync(pathname, 'utf8');
|
||||||
|
file.data = JSON.parse(jsonData);
|
||||||
|
win.webContents.send('main:collection-tree-updated', 'addEnvironmentFile', file);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeEnvironmentFile = async (win, pathname, collectionUid) => {
|
||||||
|
try {
|
||||||
|
const file = {
|
||||||
|
meta: {
|
||||||
|
collectionUid,
|
||||||
|
pathname,
|
||||||
|
name: path.basename(pathname),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const jsonData = fs.readFileSync(pathname, 'utf8');
|
||||||
|
file.data = JSON.parse(jsonData);
|
||||||
|
win.webContents.send('main:collection-tree-updated', 'changeEnvironmentFile', file);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const unlinkEnvironmentFile = async (win, pathname, collectionUid) => {
|
||||||
|
try {
|
||||||
|
const file = {
|
||||||
|
meta: {
|
||||||
|
collectionUid,
|
||||||
|
pathname,
|
||||||
|
name: path.basename(pathname),
|
||||||
|
},
|
||||||
|
data: []
|
||||||
|
};
|
||||||
|
|
||||||
|
win.webContents.send('main:collection-tree-updated', 'changeEnvironmentFile', file);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const add = async (win, pathname, collectionUid, collectionPath) => {
|
||||||
const isJson = hasJsonExtension(pathname);
|
const isJson = hasJsonExtension(pathname);
|
||||||
console.log(`watcher add: ${pathname}`);
|
console.log(`watcher add: ${pathname}`);
|
||||||
|
|
||||||
if(isJson) {
|
if(isJson) {
|
||||||
|
if(isEnvironmentConfig(pathname, collectionPath)) {
|
||||||
|
return addEnvironmentFile(win, pathname, collectionUid);
|
||||||
|
}
|
||||||
|
|
||||||
const file = {
|
const file = {
|
||||||
meta: {
|
meta: {
|
||||||
collectionUid,
|
collectionUid,
|
||||||
@ -38,8 +102,13 @@ const addDirectory = (win, pathname, collectionUid) => {
|
|||||||
win.webContents.send('main:collection-tree-updated', 'addDir', directory);
|
win.webContents.send('main:collection-tree-updated', 'addDir', directory);
|
||||||
};
|
};
|
||||||
|
|
||||||
const change = async (win, pathname, collectionUid) => {
|
const change = async (win, pathname, collectionUid, collectionPath) => {
|
||||||
console.log(`watcher change: ${pathname}`);
|
console.log(`watcher change: ${pathname}`);
|
||||||
|
try {
|
||||||
|
if(isEnvironmentConfig(pathname, collectionPath)) {
|
||||||
|
return changeEnvironmentFile(win, pathname, collectionUid);
|
||||||
|
}
|
||||||
|
|
||||||
const file = {
|
const file = {
|
||||||
meta: {
|
meta: {
|
||||||
collectionUid,
|
collectionUid,
|
||||||
@ -48,7 +117,6 @@ const change = async (win, pathname, collectionUid) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
|
||||||
const jsonData = fs.readFileSync(pathname, 'utf8');
|
const jsonData = fs.readFileSync(pathname, 'utf8');
|
||||||
file.data = await JSON.parse(jsonData);
|
file.data = await JSON.parse(jsonData);
|
||||||
win.webContents.send('main:collection-tree-updated', 'change', file);
|
win.webContents.send('main:collection-tree-updated', 'change', file);
|
||||||
@ -57,7 +125,11 @@ const change = async (win, pathname, collectionUid) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const unlink = (win, pathname, collectionUid) => {
|
const unlink = (win, pathname, collectionUid, collectionPath) => {
|
||||||
|
if(isEnvironmentConfig(pathname, collectionPath)) {
|
||||||
|
return unlinkEnvironmentFile(win, pathname, collectionUid);
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`watcher unlink: ${pathname}`);
|
console.log(`watcher unlink: ${pathname}`);
|
||||||
const file = {
|
const file = {
|
||||||
meta: {
|
meta: {
|
||||||
@ -107,11 +179,11 @@ class Watcher {
|
|||||||
});
|
});
|
||||||
|
|
||||||
watcher
|
watcher
|
||||||
.on('add', pathname => add(win, pathname, collectionUid))
|
.on('add', pathname => add(win, pathname, collectionUid, watchPath))
|
||||||
.on('addDir', pathname => addDirectory(win, pathname, collectionUid))
|
.on('addDir', pathname => addDirectory(win, pathname, collectionUid, watchPath))
|
||||||
.on('change', pathname => change(win, pathname, collectionUid))
|
.on('change', pathname => change(win, pathname, collectionUid, watchPath))
|
||||||
.on('unlink', pathname => unlink(win, pathname, collectionUid))
|
.on('unlink', pathname => unlink(win, pathname, collectionUid, watchPath))
|
||||||
.on('unlinkDir', pathname => unlinkDir(win, pathname, collectionUid))
|
.on('unlinkDir', pathname => unlinkDir(win, pathname, collectionUid, watchPath))
|
||||||
|
|
||||||
self.watchers[watchPath] = watcher;
|
self.watchers[watchPath] = watcher;
|
||||||
}, 100);
|
}, 100);
|
||||||
|
@ -85,6 +85,18 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// save environment
|
||||||
|
ipcMain.handle('renderer:save-environment', async (event, collectionPathname, environments) => {
|
||||||
|
try {
|
||||||
|
const envFilePath = path.join(collectionPathname, 'environments.json');
|
||||||
|
|
||||||
|
const content = await stringifyJson(environments);
|
||||||
|
await writeFile(envFilePath, content);
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// rename item
|
// rename item
|
||||||
ipcMain.handle('renderer:rename-item', async (event, oldPath, newPath, newName) => {
|
ipcMain.handle('renderer:rename-item', async (event, oldPath, newPath, newName) => {
|
||||||
try {
|
try {
|
||||||
|
@ -16,6 +16,8 @@ const environmentSchema = Yup.object({
|
|||||||
variables: Yup.array().of(environmentVariablesSchema).required('variables are required')
|
variables: Yup.array().of(environmentVariablesSchema).required('variables are required')
|
||||||
}).noUnknown(true).strict();
|
}).noUnknown(true).strict();
|
||||||
|
|
||||||
|
const environmentsSchema = Yup.array().of(environmentSchema);
|
||||||
|
|
||||||
const keyValueSchema = Yup.object({
|
const keyValueSchema = Yup.object({
|
||||||
uid: uidSchema,
|
uid: uidSchema,
|
||||||
name: Yup.string().nullable().max(256, 'name must be 256 characters or less'),
|
name: Yup.string().nullable().max(256, 'name must be 256 characters or less'),
|
||||||
@ -75,12 +77,14 @@ const collectionSchema = Yup.object({
|
|||||||
.length(21, 'activeEnvironmentUid must be 21 characters in length')
|
.length(21, 'activeEnvironmentUid must be 21 characters in length')
|
||||||
.matches(/^[a-zA-Z0-9]*$/, 'uid must be alphanumeric')
|
.matches(/^[a-zA-Z0-9]*$/, 'uid must be alphanumeric')
|
||||||
.nullable(),
|
.nullable(),
|
||||||
environments: Yup.array().of(environmentSchema),
|
environments: environmentsSchema,
|
||||||
pathname: Yup.string().max(1024, 'pathname cannot be more than 1024 characters').nullable()
|
pathname: Yup.string().max(1024, 'pathname cannot be more than 1024 characters').nullable()
|
||||||
}).noUnknown(true).strict();
|
}).noUnknown(true).strict();
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
requestSchema,
|
requestSchema,
|
||||||
itemSchema,
|
itemSchema,
|
||||||
|
environmentsSchema,
|
||||||
collectionSchema
|
collectionSchema
|
||||||
};
|
};
|
@ -1,8 +1,9 @@
|
|||||||
const { workspaceSchema } = require("./workspaces");
|
const { workspaceSchema } = require("./workspaces");
|
||||||
const { collectionSchema, itemSchema } = require("./collections");
|
const { collectionSchema, itemSchema, environmentsSchema } = require("./collections");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
itemSchema,
|
itemSchema,
|
||||||
|
environmentsSchema,
|
||||||
collectionSchema,
|
collectionSchema,
|
||||||
workspaceSchema
|
workspaceSchema
|
||||||
};
|
};
|
Loading…
Reference in New Issue
Block a user