Skip to content

Commit c26b807

Browse files
committed
feat: add favorite feature for spaces, add favorite spaces in menu
closes #223
1 parent 8afb4e0 commit c26b807

25 files changed

+410
-13
lines changed

public/app/config/channels.js

+1
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,5 @@ module.exports = {
4848
SET_SYNC_MODE_CHANNEL: 'sync:mode:set',
4949
GET_USER_MODE_CHANNEL: 'user:mode:get',
5050
SET_USER_MODE_CHANNEL: 'user:mode:set',
51+
SET_SPACE_AS_FAVORITE_CHANNEL: 'user:set-space-favorite:set',
5152
};

public/app/config/config.js

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ const DEFAULT_USER = {
6262
syncMode: DEFAULT_SYNC_MODE,
6363
userMode: DEFAULT_USER_MODE,
6464
},
65+
favoriteSpaces: [],
6566
};
6667

6768
const ANONYMOUS_USERNAME = 'Anonymous';

public/app/config/messages.js

+3
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ const ERROR_SETTING_SYNC_MODE =
5959
'There was an error setting the sync mode for Space Synchronization';
6060
const ERROR_GETTING_USER_MODE = 'There was an error getting the user mode';
6161
const ERROR_SETTING_USER_MODE = 'There was an error setting the user mode';
62+
const ERROR_SETTING_SPACE_AS_FAVORITE =
63+
'There was an error setting space as favorite';
6264

6365
module.exports = {
6466
ERROR_GETTING_DEVELOPER_MODE,
@@ -105,4 +107,5 @@ module.exports = {
105107
ERROR_SETTING_SYNC_MODE,
106108
ERROR_SETTING_USER_MODE,
107109
ERROR_GETTING_USER_MODE,
110+
ERROR_SETTING_SPACE_AS_FAVORITE,
108111
};

public/app/listeners/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const getAppInstanceResources = require('./getAppInstanceResources');
2929
const postAppInstanceResource = require('./postAppInstanceResource');
3030
const patchAppInstanceResource = require('./patchAppInstanceResource');
3131
const getAppInstance = require('./getAppInstance');
32+
const setSpaceAsFavorite = require('./setSpaceAsFavorite');
3233

3334
module.exports = {
3435
loadSpace,
@@ -62,4 +63,5 @@ module.exports = {
6263
getAppInstance,
6364
getUserMode,
6465
setUserMode,
66+
setSpaceAsFavorite,
6567
};
+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
const { SET_SPACE_AS_FAVORITE_CHANNEL } = require('../config/channels');
2+
const logger = require('../logger');
3+
const { ERROR_GENERAL } = require('../config/errors');
4+
5+
const setSpaceAsFavorite = (mainWindow, db) => async (
6+
event,
7+
{ spaceId, favorite }
8+
) => {
9+
// add spaceId if does not exist
10+
try {
11+
if (favorite) {
12+
const foundId = db
13+
.get('user.favoriteSpaces')
14+
.find(id => id === spaceId)
15+
.value();
16+
if (!foundId) {
17+
db.get('user.favoriteSpaces')
18+
.push(spaceId)
19+
.write();
20+
}
21+
}
22+
// remove spaceId if exists
23+
else {
24+
const foundId = db
25+
.get('user.favoriteSpaces')
26+
.find(id => id === spaceId)
27+
.value();
28+
if (foundId) {
29+
db.get('user.favoriteSpaces')
30+
.remove(id => id === spaceId)
31+
.write();
32+
}
33+
}
34+
mainWindow.webContents.send(SET_SPACE_AS_FAVORITE_CHANNEL, {
35+
favorite,
36+
spaceId,
37+
});
38+
} catch (e) {
39+
logger.error(e);
40+
mainWindow.webContents.send(SET_SPACE_AS_FAVORITE_CHANNEL, ERROR_GENERAL);
41+
}
42+
};
43+
44+
module.exports = setSpaceAsFavorite;

public/electron.js

+5
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ const {
5656
IS_AUTHENTICATED_CHANNEL,
5757
GET_USER_MODE_CHANNEL,
5858
SET_USER_MODE_CHANNEL,
59+
SET_SPACE_AS_FAVORITE_CHANNEL,
5960
} = require('./app/config/channels');
6061
const env = require('./env.json');
6162
const {
@@ -90,6 +91,7 @@ const {
9091
getSyncMode,
9192
setUserMode,
9293
getUserMode,
94+
setSpaceAsFavorite,
9395
} = require('./app/listeners');
9496
const isMac = require('./app/utils/isMac');
9597

@@ -404,6 +406,9 @@ app.on('ready', async () => {
404406
// called when getting sync mode
405407
ipcMain.on(GET_SYNC_MODE_CHANNEL, getSyncMode(mainWindow, db));
406408

409+
// called when setting developer mode
410+
ipcMain.on(SET_SPACE_AS_FAVORITE_CHANNEL, setSpaceAsFavorite(mainWindow, db));
411+
407412
// called when getting geolocation enabled
408413
ipcMain.on(
409414
GET_GEOLOCATION_ENABLED_CHANNEL,

src/App.js

+7
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import SpacesNearby from './components/SpacesNearby';
1515
import Settings from './components/Settings';
1616
import LoadSpace from './components/LoadSpace';
1717
import SpaceScreen from './components/space/SpaceScreen';
18+
import FavoriteSpaces from './components/FavoriteSpaces';
1819
import SyncScreen from './components/space/SyncScreen';
1920
import DeveloperScreen from './components/developer/DeveloperScreen';
2021
import { OnlineTheme, OfflineTheme } from './themes';
@@ -32,6 +33,7 @@ import {
3233
DEVELOPER_PATH,
3334
DASHBOARD_PATH,
3435
SIGN_IN_PATH,
36+
FAVORITE_PATH,
3537
} from './config/paths';
3638
import {
3739
getGeolocation,
@@ -161,6 +163,11 @@ export class App extends Component {
161163
path={HOME_PATH}
162164
component={Authorization()(Home)}
163165
/>
166+
<Route
167+
exact
168+
path={FAVORITE_PATH}
169+
component={Authorization()(FavoriteSpaces)}
170+
/>
164171
<Route
165172
exact
166173
path={SPACES_NEARBY_PATH}

src/actions/user.js

+30
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import {
2424
FLAG_SETTING_USER_MODE,
2525
GET_USER_MODE_SUCCEEDED,
2626
SET_USER_MODE_SUCCEEDED,
27+
FLAG_SETTING_SPACE_AS_FAVORITE,
28+
SET_SPACE_AS_FAVORITE_SUCCEEDED,
2729
} from '../types';
2830
import {
2931
ERROR_GETTING_GEOLOCATION,
@@ -39,6 +41,7 @@ import {
3941
ERROR_SETTING_SYNC_MODE,
4042
ERROR_GETTING_USER_MODE,
4143
ERROR_SETTING_USER_MODE,
44+
ERROR_SETTING_SPACE_AS_FAVORITE,
4245
} from '../config/messages';
4346
import {
4447
GET_USER_FOLDER_CHANNEL,
@@ -52,6 +55,7 @@ import {
5255
SET_SYNC_MODE_CHANNEL,
5356
GET_USER_MODE_CHANNEL,
5457
SET_USER_MODE_CHANNEL,
58+
SET_SPACE_AS_FAVORITE_CHANNEL,
5559
} from '../config/channels';
5660
import { createFlag } from './common';
5761
import { ERROR_GENERAL } from '../config/errors';
@@ -71,6 +75,7 @@ const flagGettingSyncMode = createFlag(FLAG_GETTING_SYNC_MODE);
7175
const flagSettingSyncMode = createFlag(FLAG_SETTING_SYNC_MODE);
7276
const flagGettingUserMode = createFlag(FLAG_GETTING_USER_MODE);
7377
const flagSettingUserMode = createFlag(FLAG_SETTING_USER_MODE);
78+
const flagSettingSpaceAsFavorite = createFlag(FLAG_SETTING_SPACE_AS_FAVORITE);
7479

7580
const getGeolocation = async () => async dispatch => {
7681
// only fetch location if online
@@ -344,6 +349,30 @@ const setUserMode = async userMode => dispatch => {
344349
}
345350
};
346351

352+
const setSpaceAsFavorite = async payload => dispatch => {
353+
try {
354+
dispatch(flagSettingSpaceAsFavorite(true));
355+
window.ipcRenderer.send(SET_SPACE_AS_FAVORITE_CHANNEL, payload);
356+
window.ipcRenderer.once(
357+
SET_SPACE_AS_FAVORITE_CHANNEL,
358+
(event, favorite) => {
359+
if (favorite === ERROR_GENERAL) {
360+
toastr.error(ERROR_MESSAGE_HEADER, ERROR_SETTING_SPACE_AS_FAVORITE);
361+
} else {
362+
dispatch({
363+
type: SET_SPACE_AS_FAVORITE_SUCCEEDED,
364+
payload,
365+
});
366+
}
367+
dispatch(flagSettingSpaceAsFavorite(false));
368+
}
369+
);
370+
} catch (e) {
371+
console.error(e);
372+
toastr.error(ERROR_MESSAGE_HEADER, ERROR_SETTING_SPACE_AS_FAVORITE);
373+
}
374+
};
375+
347376
export {
348377
getUserFolder,
349378
getGeolocation,
@@ -357,4 +386,5 @@ export {
357386
setSyncMode,
358387
getUserMode,
359388
setUserMode,
389+
setSpaceAsFavorite,
360390
};

src/components/FavoriteSpaces.js

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import React, { Component } from 'react';
2+
import { connect } from 'react-redux';
3+
import PropTypes from 'prop-types';
4+
import { Set, List } from 'immutable';
5+
import { withRouter } from 'react-router';
6+
import classNames from 'classnames';
7+
import { withStyles } from '@material-ui/core/styles';
8+
import CssBaseline from '@material-ui/core/CssBaseline';
9+
import AppBar from '@material-ui/core/AppBar';
10+
import Toolbar from '@material-ui/core/Toolbar';
11+
import Styles from '../Styles';
12+
import SpaceGrid from './space/SpaceGrid';
13+
import Loader from './common/Loader';
14+
import Main from './common/Main';
15+
import { SPACES_NEARBY_MAIN_ID } from '../config/selectors';
16+
import { searchSpacesByQuery } from '../utils/search';
17+
import { getSpaces } from '../actions';
18+
19+
class FavoriteSpaces extends Component {
20+
static propTypes = {
21+
classes: PropTypes.shape({
22+
root: PropTypes.string.isRequired,
23+
appBarShift: PropTypes.string.isRequired,
24+
menuButton: PropTypes.string.isRequired,
25+
hide: PropTypes.string.isRequired,
26+
drawer: PropTypes.string.isRequired,
27+
drawerPaper: PropTypes.string.isRequired,
28+
drawerHeader: PropTypes.string.isRequired,
29+
content: PropTypes.string.isRequired,
30+
contentShift: PropTypes.string.isRequired,
31+
settings: PropTypes.string.isRequired,
32+
}).isRequired,
33+
theme: PropTypes.shape({
34+
direction: PropTypes.string.isRequired,
35+
}).isRequired,
36+
history: PropTypes.shape({
37+
replace: PropTypes.func.isRequired,
38+
}).isRequired,
39+
spaces: PropTypes.instanceOf(Set).isRequired,
40+
activity: PropTypes.bool,
41+
favoriteSpaces: PropTypes.instanceOf(List).isRequired,
42+
searchQuery: PropTypes.string.isRequired,
43+
dispatchGetSpaces: PropTypes.func.isRequired,
44+
};
45+
46+
static defaultProps = {
47+
activity: false,
48+
};
49+
50+
state = {
51+
filteredSpaces: Set(),
52+
};
53+
54+
async componentDidMount() {
55+
const { dispatchGetSpaces } = this.props;
56+
await dispatchGetSpaces();
57+
}
58+
59+
componentDidUpdate({
60+
spaces: prevSpaces,
61+
searchQuery: prevSearchQuery,
62+
favoriteSpaces: prevFavoriteSpaces,
63+
}) {
64+
const { spaces, searchQuery, favoriteSpaces } = this.props;
65+
if (
66+
!spaces.equals(prevSpaces) ||
67+
searchQuery !== prevSearchQuery ||
68+
!favoriteSpaces.equals(prevFavoriteSpaces)
69+
) {
70+
this.filterSpaces();
71+
}
72+
}
73+
74+
filterSpaces = () => {
75+
const { spaces, searchQuery, favoriteSpaces } = this.props;
76+
let filteredSpaces = searchSpacesByQuery(spaces, searchQuery);
77+
filteredSpaces = filteredSpaces.filter(({ id }) =>
78+
favoriteSpaces.includes(id)
79+
);
80+
this.setState({ filteredSpaces });
81+
};
82+
83+
render() {
84+
const { classes, activity } = this.props;
85+
const { filteredSpaces } = this.state;
86+
87+
if (activity) {
88+
return (
89+
<div className={classNames(classes.root)} style={{ height: '100%' }}>
90+
<CssBaseline />
91+
<AppBar position="fixed">
92+
<Toolbar />
93+
</AppBar>
94+
<main className="Main">
95+
<Loader />
96+
</main>
97+
</div>
98+
);
99+
}
100+
101+
return (
102+
<Main
103+
showSearch
104+
handleOnSearch={this.handleOnSearch}
105+
id={SPACES_NEARBY_MAIN_ID}
106+
>
107+
<SpaceGrid spaces={filteredSpaces} showActions saved />
108+
</Main>
109+
);
110+
}
111+
}
112+
113+
const mapStateToProps = ({ authentication, Space }) => ({
114+
searchQuery: Space.get('searchQuery'),
115+
activity: Boolean(Space.getIn(['current', 'activity']).size),
116+
favoriteSpaces: authentication.getIn(['user', 'favoriteSpaces']),
117+
spaces: Space.get('saved'),
118+
});
119+
120+
const mapDispatchToProps = {
121+
dispatchGetSpaces: getSpaces,
122+
};
123+
124+
const ConnectedComponent = connect(
125+
mapStateToProps,
126+
mapDispatchToProps
127+
)(FavoriteSpaces);
128+
129+
const StyledComponent = withStyles(Styles, { withTheme: true })(
130+
ConnectedComponent
131+
);
132+
133+
export default withRouter(StyledComponent);

src/components/common/MainMenu.js

+14
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import ListItemText from '@material-ui/core/ListItemText';
1313
import AccountCircle from '@material-ui/icons/AccountCircle';
1414
import List from '@material-ui/core/List';
1515
import ShowChartIcon from '@material-ui/icons/ShowChart';
16+
import FavoriteIcon from '@material-ui/icons/Star';
1617
import PublishIcon from '@material-ui/icons/Publish';
1718
import SettingsIcon from '@material-ui/icons/Settings';
1819
import { Online, Offline } from 'react-detect-offline';
@@ -26,6 +27,7 @@ import {
2627
DEVELOPER_PATH,
2728
DASHBOARD_PATH,
2829
SIGN_IN_PATH,
30+
FAVORITE_PATH,
2931
} from '../../config/paths';
3032
import {
3133
SETTINGS_MENU_ITEM_ID,
@@ -36,6 +38,7 @@ import {
3638
DASHBOARD_MENU_ITEM_ID,
3739
DEVELOPER_MENU_ITEM_ID,
3840
SIGN_OUT_MENU_ITEM_ID,
41+
FAVORITE_MENU_ITEM_ID,
3942
} from '../../config/selectors';
4043
import { signOut } from '../../actions/authentication';
4144
import { AUTHENTICATED } from '../../config/constants';
@@ -189,6 +192,17 @@ export class MainMenu extends Component {
189192
</ListItemIcon>
190193
<ListItemText primary={t('Saved Spaces')} />
191194
</MenuItem>
195+
<MenuItem
196+
id={FAVORITE_MENU_ITEM_ID}
197+
onClick={() => this.handleClick(FAVORITE_PATH)}
198+
button
199+
selected={path === FAVORITE_PATH}
200+
>
201+
<ListItemIcon>
202+
<FavoriteIcon />
203+
</ListItemIcon>
204+
<ListItemText primary={t('Favorite Spaces')} />
205+
</MenuItem>
192206
{this.renderOfflineMenuItem(
193207
<MenuItem
194208
id={SPACES_NEARBY_MENU_ITEM_ID}

0 commit comments

Comments
 (0)