feat: local collections environment sync

This commit is contained in:
Anoop M D 2022-10-18 03:39:36 +05:30
parent ff87586a1d
commit e98f219448
7 changed files with 209 additions and 43 deletions

View File

@ -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) => {

View File

@ -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);
});
};

View File

@ -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;

View File

@ -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);

View File

@ -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 {

View File

@ -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
}; };

View File

@ -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
}; };