feat: wireup local collections open and create buttons

This commit is contained in:
Anoop M D 2022-10-18 01:27:53 +05:30
parent 9fae7f72d4
commit 8a96a0ce71
8 changed files with 173 additions and 36 deletions

View File

@ -1,4 +1,4 @@
import React, { useState } from 'react'; import React from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import find from 'lodash/find'; import find from 'lodash/find';
import filter from 'lodash/filter'; import filter from 'lodash/filter';

View File

@ -1,10 +1,18 @@
import React, { useRef, useEffect } from 'react'; import React, { useRef, useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { useFormik } from 'formik'; import { useFormik } from 'formik';
import * as Yup from 'yup'; import * as Yup from 'yup';
import { browserLocalDirectory } from 'providers/ReduxStore/slices/collections/actions';
import { isElectron } from 'utils/common/platform';
import { createCollection, createLocalCollection } from 'providers/ReduxStore/slices/collections/actions';
import toast from 'react-hot-toast';
import Modal from 'components/Modal'; import Modal from 'components/Modal';
const CreateCollection = ({handleConfirm, handleCancel}) => { const CreateCollection = ({onClose, isLocal}) => {
const inputRef = useRef(); const inputRef = useRef();
const dispatch = useDispatch();
const isPlatformElectron = isElectron();
const formik = useFormik({ const formik = useFormik({
enableReinitialize: true, enableReinitialize: true,
initialValues: { initialValues: {
@ -18,9 +26,26 @@ const CreateCollection = ({handleConfirm, handleCancel}) => {
.required('name is required') .required('name is required')
}), }),
onSubmit: (values) => { onSubmit: (values) => {
handleConfirm(values); const action = isLocal && isPlatformElectron ? createLocalCollection : createCollection;
dispatch(action(values.collectionName, values.collectionLocation))
.then(() => {
toast.success("Collection created");
onClose();
})
.catch(() => toast.error("An error occured while creating the collection"));
} }
}); });
const browse = () => {
dispatch(browserLocalDirectory())
.then((dirPath) => {
formik.setFieldValue('collectionLocation', dirPath);
})
.catch((error) => {
formik.setFieldValue('collectionLocation', '');
console.error(error);
});
};
useEffect(() => { useEffect(() => {
if(inputRef && inputRef.current) { if(inputRef && inputRef.current) {
@ -36,7 +61,7 @@ const CreateCollection = ({handleConfirm, handleCancel}) => {
title='Create Collection' title='Create Collection'
confirmText='Create' confirmText='Create'
handleConfirm={onSubmit} handleConfirm={onSubmit}
handleCancel={handleCancel} handleCancel={onClose}
> >
<form className="bruno-form" onSubmit={formik.handleSubmit}> <form className="bruno-form" onSubmit={formik.handleSubmit}>
<div> <div>
@ -54,6 +79,31 @@ const CreateCollection = ({handleConfirm, handleCancel}) => {
{formik.touched.collectionName && formik.errors.collectionName ? ( {formik.touched.collectionName && formik.errors.collectionName ? (
<div className="text-red-500">{formik.errors.collectionName}</div> <div className="text-red-500">{formik.errors.collectionName}</div>
) : null} ) : null}
{isLocal && isPlatformElectron ? (
<>
<label htmlFor="collectionLocation" className="block font-semibold mt-3">Location</label>
<input
id="collection-location"
type="text"
name="collectionLocation"
readOnly={true}
className="block textbox mt-2 w-full"
autoComplete="off" autoCorrect="off" autoCapitalize="off" spellCheck="false"
value={formik.values.collectionLocation || ''}
onClick={browse}
/>
</>
) : null}
{isLocal && isPlatformElectron && formik.touched.collectionLocation && formik.errors.collectionLocation ? (
<div className="text-red-500">{formik.errors.collectionLocation}</div>
) : null}
{isLocal && isPlatformElectron ? (
<div className="mt-1">
<span className="text-link cursor-pointer hover:underline" onClick={browse}>Browse</span>
</div>
) : null }
</div> </div>
</form> </form>
</Modal> </Modal>

View File

@ -1,15 +1,19 @@
import React, { useRef, forwardRef } from 'react'; import React, { useState, useRef, forwardRef } from 'react';
import filter from 'lodash/filter'; import filter from 'lodash/filter';
import { useSelector } from 'react-redux'; import { useSelector, useDispatch } from 'react-redux';
import Dropdown from 'components/Dropdown'; import Dropdown from 'components/Dropdown';
import { openLocalCollection } from 'providers/ReduxStore/slices/collections/actions';
import { IconArrowForwardUp, IconCaretDown, IconFolders, IconPlus } from '@tabler/icons'; import { IconArrowForwardUp, IconCaretDown, IconFolders, IconPlus } from '@tabler/icons';
import Collection from '../Collections/Collection'; import Collection from '../Collections/Collection';
import CreateCollection from '../CreateCollection';
import { isLocalCollection } from 'utils/collections'; import { isLocalCollection } from 'utils/collections';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
const LocalCollections = ({searchText}) => { const LocalCollections = ({searchText}) => {
const dropdownTippyRef = useRef(); const dropdownTippyRef = useRef();
const dispatch = useDispatch();
const { collections } = useSelector((state) => state.collections); const { collections } = useSelector((state) => state.collections);
const [createCollectionModalOpen, setCreateCollectionModalOpen] = useState(false);
const collectionToDisplay = filter(collections, (c) => isLocalCollection(c)); const collectionToDisplay = filter(collections, (c) => isLocalCollection(c));
@ -35,17 +39,30 @@ const LocalCollections = ({searchText}) => {
const onDropdownCreate = (ref) => dropdownTippyRef.current = ref; const onDropdownCreate = (ref) => dropdownTippyRef.current = ref;
const handleOpenLocalCollection = () => {
dispatch(openLocalCollection())
.catch((err) => console.log(err) && toast.error("An error occured while opening the local collection"));
}
return ( return (
<StyledWrapper> <StyledWrapper>
{createCollectionModalOpen ? (
<CreateCollection
isLocal={true}
onClose={() => setCreateCollectionModalOpen(false)}
/>
) : null}
<div className="items-center cursor-pointer mt-6 relative"> <div className="items-center cursor-pointer mt-6 relative">
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement='bottom-end'> <Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement='bottom-end'>
<div className="dropdown-item" onClick={() => {}}> <div className="dropdown-item" onClick={() => setCreateCollectionModalOpen(true)}>
<div className="pr-2 text-gray-600"> <div className="pr-2 text-gray-600">
<IconPlus size={18} strokeWidth={1.5}/> <IconPlus size={18} strokeWidth={1.5}/>
</div> </div>
<span>Create Collection</span> <span>Create Collection</span>
</div> </div>
<div className="dropdown-item" onClick={() => {}}> <div className="dropdown-item" onClick={handleOpenLocalCollection}>
<div className="pr-2 text-gray-600"> <div className="pr-2 text-gray-600">
<IconArrowForwardUp size={18} strokeWidth={1.5}/> <IconArrowForwardUp size={18} strokeWidth={1.5}/>
</div> </div>

View File

@ -1,6 +1,15 @@
import styled from 'styled-components'; import styled from 'styled-components';
const StyledWrapper = styled.div` const StyledWrapper = styled.div`
.local-collection-label {
background-color: var(--color-sidebar-background);
}
.local-collections-unavailable {
padding: 0.35rem 0.6rem;
border-top: solid 1px #ddd;
font-size: 11px;
}
.collection-dropdown { .collection-dropdown {
color: rgb(110 110 110); color: rgb(110 110 110);

View File

@ -2,21 +2,24 @@ import React, { useState, forwardRef, useRef } from 'react';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import Dropdown from 'components/Dropdown'; import Dropdown from 'components/Dropdown';
import Bruno from 'components/Bruno'; import Bruno from 'components/Bruno';
import { IconFolders } from '@tabler/icons';
import { useSelector, useDispatch } from 'react-redux'; import { useSelector, useDispatch } from 'react-redux';
import { collectionImported } from 'providers/ReduxStore/slices/collections'; import { collectionImported } from 'providers/ReduxStore/slices/collections';
import { createCollection } from 'providers/ReduxStore/slices/collections/actions'; import { openLocalCollection } from 'providers/ReduxStore/slices/collections/actions';
import { addCollectionToWorkspace } from 'providers/ReduxStore/slices/workspaces/actions'; import { addCollectionToWorkspace } from 'providers/ReduxStore/slices/workspaces/actions';
import { showHomePage } from 'providers/ReduxStore/slices/app'; import { showHomePage } from 'providers/ReduxStore/slices/app';
import { IconDots } from '@tabler/icons'; import { IconDots } from '@tabler/icons';
import CreateCollection from '../CreateCollection'; import CreateCollection from '../CreateCollection';
import SelectCollection from 'components/Sidebar/Collections/SelectCollection'; import SelectCollection from 'components/Sidebar/Collections/SelectCollection';
import importCollection from 'utils/collections/import'; import importCollection from 'utils/collections/import';
import { isElectron } from 'utils/common/platform';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
const TitleBar = () => { const TitleBar = () => {
const [createCollectionModalOpen, setCreateCollectionModalOpen] = useState(false); const [createCollectionModalOpen, setCreateCollectionModalOpen] = useState(false);
const [addCollectionToWSModalOpen, setAddCollectionToWSModalOpen] = useState(false); const [addCollectionToWSModalOpen, setAddCollectionToWSModalOpen] = useState(false);
const { activeWorkspaceUid } = useSelector((state) => state.workspaces); const { activeWorkspaceUid } = useSelector((state) => state.workspaces);
const isPlatformElectron = isElectron();
const dispatch = useDispatch(); const dispatch = useDispatch();
const menuDropdownTippyRef = useRef(); const menuDropdownTippyRef = useRef();
@ -31,14 +34,10 @@ const TitleBar = () => {
const handleTitleClick = () => dispatch(showHomePage()); const handleTitleClick = () => dispatch(showHomePage());
const handleCreateCollection = (values) => { const handleOpenLocalCollection = () => {
setCreateCollectionModalOpen(false); dispatch(openLocalCollection())
dispatch(createCollection(values.collectionName)) .catch((err) => console.log(err) && toast.error("An error occured while opening the local collection"));
.then(() => { }
toast.success("Collection created");
})
.catch(() => toast.error("An error occured while creating the collection"));
};
const handleAddCollectionToWorkspace = (collectionUid) => { const handleAddCollectionToWorkspace = (collectionUid) => {
setAddCollectionToWSModalOpen(false); setAddCollectionToWSModalOpen(false);
@ -62,8 +61,8 @@ const TitleBar = () => {
<StyledWrapper className="px-2 py-2"> <StyledWrapper className="px-2 py-2">
{createCollectionModalOpen ? ( {createCollectionModalOpen ? (
<CreateCollection <CreateCollection
handleCancel={() => setCreateCollectionModalOpen(false)} isLocal={createCollectionModalOpen === 'local' ? true : false}
handleConfirm={handleCreateCollection} onClose={() => setCreateCollectionModalOpen(false)}
/> />
) : null} ) : null}
@ -106,6 +105,37 @@ const TitleBar = () => {
}}> }}>
Add Collection to Workspace Add Collection to Workspace
</div> </div>
{isPlatformElectron ? (
<>
<div className="font-medium label-item font-medium local-collection-label">
<div className='flex items-center'>
<span className='mr-2'>
<IconFolders size={18} strokeWidth={1.5}/>
</span>
<span>
Local Collections
</span>
</div>
</div>
<div className="dropdown-item" onClick={(e) => {
setCreateCollectionModalOpen('local');
menuDropdownTippyRef.current.hide();
}}>
Create Local Collection
</div>
<div className="dropdown-item" onClick={(e) => {
handleOpenLocalCollection();
menuDropdownTippyRef.current.hide();
}}>
Open Local Collection
</div>
</>
) : (
<div className="flex items-center select-none text-gray-400 text-xs local-collections-unavailable">
Note: Local collections are only available on the desktop app.
</div>
)}
</Dropdown> </Dropdown>
</div> </div>
</div> </div>

View File

@ -12,7 +12,7 @@ import {
} from '@tabler/icons'; } from '@tabler/icons';
import { useSelector, useDispatch } from 'react-redux'; import { useSelector, useDispatch } from 'react-redux';
import { collectionImported } from 'providers/ReduxStore/slices/collections'; import { collectionImported } from 'providers/ReduxStore/slices/collections';
import { createCollection } from 'providers/ReduxStore/slices/collections/actions'; import { openLocalCollection } from 'providers/ReduxStore/slices/collections/actions';
import { addCollectionToWorkspace } from 'providers/ReduxStore/slices/workspaces/actions'; import { addCollectionToWorkspace } from 'providers/ReduxStore/slices/workspaces/actions';
import Bruno from 'components/Bruno'; import Bruno from 'components/Bruno';
import CreateCollection from 'components/Sidebar/CreateCollection'; import CreateCollection from 'components/Sidebar/CreateCollection';
@ -29,15 +29,6 @@ const Welcome = () => {
const { activeWorkspaceUid } = useSelector((state) => state.workspaces); const { activeWorkspaceUid } = useSelector((state) => state.workspaces);
const isPlatformElectron = isElectron(); const isPlatformElectron = isElectron();
const handleCreateCollection = (values) => {
setCreateCollectionModalOpen(false);
dispatch(createCollection(values.collectionName))
.then(() => {
toast.success("Collection created");
})
.catch(() => toast.error("An error occured while creating the collection"));
};
const handleAddCollectionToWorkspace = (collectionUid) => { const handleAddCollectionToWorkspace = (collectionUid) => {
setAddCollectionToWSModalOpen(false); setAddCollectionToWSModalOpen(false);
dispatch(addCollectionToWorkspace(activeWorkspaceUid, collectionUid)) dispatch(addCollectionToWorkspace(activeWorkspaceUid, collectionUid))
@ -69,12 +60,17 @@ const Welcome = () => {
}); });
}; };
const handleOpenLocalCollection = () => {
dispatch(openLocalCollection())
.catch((err) => console.log(err) && toast.error("An error occured while opening the local collection"));
}
return ( return (
<StyledWrapper className="pb-4 px-6 mt-6"> <StyledWrapper className="pb-4 px-6 mt-6">
{createCollectionModalOpen ? ( {createCollectionModalOpen ? (
<CreateCollection <CreateCollection
handleCancel={() => setCreateCollectionModalOpen(false)} isLocal={createCollectionModalOpen === 'local' ? true : false}
handleConfirm={handleCreateCollection} onClose={() => setCreateCollectionModalOpen(false)}
/> />
) : null} ) : null}
@ -112,10 +108,10 @@ const Welcome = () => {
{isPlatformElectron ? ( {isPlatformElectron ? (
<div className="mt-4 flex items-center collection-options select-none"> <div className="mt-4 flex items-center collection-options select-none">
<div className="flex items-center"> <div className="flex items-center">
<IconPlus size={18} strokeWidth={2}/><span className="label ml-2" onClick={() => setCreateCollectionModalOpen(true)}>Create Collection</span> <IconPlus size={18} strokeWidth={2}/><span className="label ml-2" onClick={() => setCreateCollectionModalOpen('local')}>Create Collection</span>
</div> </div>
<div className="flex items-center ml-6"> <div className="flex items-center ml-6">
<IconFolders size={18} strokeWidth={2}/><span className="label ml-2">Open Collection</span> <IconFolders size={18} strokeWidth={2}/><span className="label ml-2" onClick={handleOpenLocalCollection}>Open Collection</span>
</div> </div>
</div> </div>
) : ( ) : (

View File

@ -801,4 +801,37 @@ export const removeLocalCollection = (collectionUid) => (dispatch, getState) =>
.then(resolve) .then(resolve)
.catch(reject); .catch(reject);
}); });
};
export const browserLocalDirectory = () => (dispatch, getState) => {
const { ipcRenderer } = window;
return new Promise((resolve, reject) => {
ipcRenderer
.invoke('renderer:browse-directory')
.then(resolve)
.catch(reject);
});
}
export const createLocalCollection = (collectionName, collectionLocation) => () => {
const { ipcRenderer } = window;
return new Promise((resolve, reject) => {
ipcRenderer
.invoke('renderer:create-collection', collectionName, collectionLocation)
.then(resolve)
.catch(reject);
});
};
export const openLocalCollection = () => () => {
return new Promise((resolve, reject) => {
const { ipcRenderer } = window;
ipcRenderer
.invoke('renderer:open-collection')
.then(resolve)
.catch(reject);
});
}; };

View File

@ -30,22 +30,24 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
try { try {
const dirPath = path.join(collectionLocation, collectionName); const dirPath = path.join(collectionLocation, collectionName);
if (fs.existsSync(dirPath)){ if (fs.existsSync(dirPath)){
throw new Error(`collection: ${dir} already exists`); throw new Error(`collection: ${dirPath} already exists`);
} }
if(!isValidPathname(dirPath)) { if(!isValidPathname(dirPath)) {
throw new Error(`collection: invaid pathname - ${dir}`); throw new Error(`collection: invalid pathname - ${dir}`);
} }
await createDirectory(dirPath); await createDirectory(dirPath);
const uid = uuid();
const content = await stringifyJson({ const content = await stringifyJson({
version: '1.0', version: '1.0',
uid: uid,
name: collectionName,
type: 'collection' type: 'collection'
}); });
await writeFile(path.join(dirPath, 'bruno.json'), content); await writeFile(path.join(dirPath, 'bruno.json'), content);
const uid = uuid();
mainWindow.webContents.send('main:collection-opened', dirPath, uid); mainWindow.webContents.send('main:collection-opened', dirPath, uid);
ipcMain.emit('main:collection-opened', mainWindow, dirPath, uid); ipcMain.emit('main:collection-opened', mainWindow, dirPath, uid);