Skip to content

Commit

Permalink
feat: add selection when exporting a space
Browse files Browse the repository at this point in the history
  • Loading branch information
pyphilia committed May 12, 2020
1 parent c874cd3 commit 335a3a9
Show file tree
Hide file tree
Showing 7 changed files with 280 additions and 22 deletions.
50 changes: 37 additions & 13 deletions public/app/listeners/exportSpace.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,49 @@ const fsPromises = fs.promises;
// In the future we can add options so that the behaviour can be slightly modified
const exportSpace = (mainWindow, db) => async (
event,
{ archivePath, id, userId: user }
{
archivePath,
id,
userId,
selection: {
space: isSpaceSelected,
actions: isActionsSelected,
resources: isResourcesSelected,
},
}
) => {
try {
// get space from local database
const space = db
.get(SPACES_COLLECTION)
.find({ id })
.value();
const space = isSpaceSelected
? db
.get(SPACES_COLLECTION)
.find({ id })
.value()
: {};

// build conditions when fetching resources and actions
// teachers can fetch every user's data
// students can only fetch their own data
const isStudent = db.get('user.settings.studentMode').value();
const conditions = { spaceId: id };
if (isStudent) {
conditions.user = userId;
}

// export the user's resources, private and public
const resources = db
.get(APP_INSTANCE_RESOURCES_COLLECTION)
.filter({ user, spaceId: id })
.value();
const resources = isResourcesSelected
? db
.get(APP_INSTANCE_RESOURCES_COLLECTION)
.filter(conditions)
.value()
: [];

const actions = db
.get(ACTIONS_COLLECTION)
.filter({ user, spaceId: id })
.value();
const actions = isActionsSelected
? db
.get(ACTIONS_COLLECTION)
.filter(conditions)
.value()
: [];

// abort if space does not exist
if (!space) {
Expand Down
7 changes: 7 additions & 0 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import Settings from './components/Settings';
import LoadSpace from './components/LoadSpace';
import SpaceScreen from './components/space/SpaceScreen';
import SyncScreen from './components/space/SyncScreen';
import ExportSelectionScreen from './components/space/export/ExportSelectionScreen';
import DeveloperScreen from './components/developer/DeveloperScreen';
import { OnlineTheme, OfflineTheme } from './themes';
import Dashboard from './components/dashboard/Dashboard';
Expand All @@ -33,6 +34,7 @@ import {
DASHBOARD_PATH,
SIGN_IN_PATH,
SAVED_SPACES_PATH,
buildExportSelectionPathForSpaceId,
} from './config/paths';
import {
getGeolocation,
Expand Down Expand Up @@ -193,6 +195,11 @@ export class App extends Component {
path={SYNC_SPACE_PATH}
component={Authorization()(SyncScreen)}
/>
<Route
exact
path={buildExportSelectionPathForSpaceId()}
component={Authorization()(ExportSelectionScreen)}
/>
<Route
exact
path={SPACE_PATH}
Expand Down
3 changes: 2 additions & 1 deletion src/actions/space.js
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ const clearSpace = () => dispatch => {
});
};

const exportSpace = (id, spaceName, userId) => dispatch => {
const exportSpace = (id, spaceName, userId, selection) => dispatch => {
window.ipcRenderer.send(SHOW_EXPORT_SPACE_PROMPT_CHANNEL, spaceName);
window.ipcRenderer.once(
RESPOND_EXPORT_SPACE_PROMPT_CHANNEL,
Expand All @@ -266,6 +266,7 @@ const exportSpace = (id, spaceName, userId) => dispatch => {
archivePath,
id,
userId,
selection,
});
} else {
dispatch(flagExportingSpace(false));
Expand Down
4 changes: 2 additions & 2 deletions src/components/common/__snapshots__/MediaCard.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ exports[`<MediaCard /> <MediaCard /> with showActions = true with text defined r
<Connect(withI18nextTranslation(WithStyles(ClearButton)))
spaceId="id"
/>
<withI18nextTranslation(WithStyles(Connect(ExportButton)))
<withRouter(withI18nextTranslation(WithStyles(Connect(ExportButton))))
space={
Object {
"id": "id",
Expand Down Expand Up @@ -164,7 +164,7 @@ exports[`<MediaCard /> <MediaCard /> with showActions = true with text undefined
<Connect(withI18nextTranslation(WithStyles(ClearButton)))
spaceId="id"
/>
<withI18nextTranslation(WithStyles(Connect(ExportButton)))
<withRouter(withI18nextTranslation(WithStyles(Connect(ExportButton))))
space={
Object {
"id": "id",
Expand Down
20 changes: 14 additions & 6 deletions src/components/space/ExportButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { withTranslation } from 'react-i18next';
import { withRouter } from 'react-router';
import clsx from 'clsx';
import IconButton from '@material-ui/core/IconButton/IconButton';
import UnarchiveIcon from '@material-ui/icons/Unarchive';
Expand All @@ -10,6 +11,7 @@ import { withStyles } from '@material-ui/core';
import Styles from '../../Styles';
import { exportSpace } from '../../actions/space';
import { SPACE_EXPORT_BUTTON_CLASS } from '../../config/selectors';
import { buildExportSelectionPathForSpaceId } from '../../config/paths';

class ExportButton extends Component {
static propTypes = {
Expand All @@ -21,15 +23,21 @@ class ExportButton extends Component {
appBar: PropTypes.string.isRequired,
button: PropTypes.string.isRequired,
}).isRequired,
dispatchExportSpace: PropTypes.func.isRequired,
t: PropTypes.func.isRequired,
userId: PropTypes.string.isRequired,
history: PropTypes.shape({
push: PropTypes.func.isRequired,
}).isRequired,
};

handleExport = () => {
const { space, dispatchExportSpace, userId } = this.props;
const { id, name } = space;
dispatchExportSpace(id, name, userId);
const {
history: { push },
space,
} = this.props;
push({
pathname: buildExportSelectionPathForSpaceId(space.id),
state: { space },
});
};

render() {
Expand Down Expand Up @@ -67,4 +75,4 @@ const StyledComponent = withStyles(Styles, { withTheme: true })(

const TranslatedComponent = withTranslation()(StyledComponent);

export default TranslatedComponent;
export default withRouter(TranslatedComponent);
214 changes: 214 additions & 0 deletions src/components/space/export/ExportSelectionScreen.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import AppBar from '@material-ui/core/AppBar/AppBar';
import Toolbar from '@material-ui/core/Toolbar/Toolbar';
import { withRouter } from 'react-router';
import clsx from 'clsx';
import { withTranslation } from 'react-i18next';
import FormGroup from '@material-ui/core/FormGroup';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Checkbox from '@material-ui/core/Checkbox';
import Button from '@material-ui/core//Button';
import { withStyles } from '@material-ui/core/styles';
import CssBaseline from '@material-ui/core/CssBaseline';
import { Typography } from '@material-ui/core';
import { connect } from 'react-redux';
import { exportSpace } from '../../../actions';
import Styles from '../../../Styles';
import Loader from '../../common/Loader';
import Main from '../../common/Main';
import SpaceNotFound from '../SpaceNotFound';

const styles = theme => ({
...Styles(theme),
buttonGroup: {
textAlign: 'center',
},
});

class ExportSelectionScreen extends Component {
static propTypes = {
classes: PropTypes.shape({
root: PropTypes.string.isRequired,
appBar: PropTypes.string.isRequired,
appBarShift: PropTypes.string.isRequired,
menuButton: PropTypes.string.isRequired,
hide: PropTypes.string.isRequired,
drawer: PropTypes.string.isRequired,
drawerPaper: PropTypes.string.isRequired,
drawerHeader: PropTypes.string.isRequired,
content: PropTypes.string.isRequired,
contentShift: PropTypes.string.isRequired,
buttonGroup: PropTypes.string.isRequired,
submitButton: PropTypes.string.isRequired,
button: PropTypes.string.isRequired,
}).isRequired,
theme: PropTypes.shape({ direction: PropTypes.string }).isRequired,
dispatchExportSpace: PropTypes.func.isRequired,
activity: PropTypes.bool.isRequired,
history: PropTypes.shape({
goBack: PropTypes.func.isRequired,
length: PropTypes.number.isRequired,
}).isRequired,
userId: PropTypes.string.isRequired,
location: PropTypes.shape({
search: PropTypes.string.isRequired,
state: PropTypes.shape({
space: PropTypes.shape({
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
}),
}),
}).isRequired,
t: PropTypes.func.isRequired,
};

state = {
space: true,
actions: true,
resources: true,
};

handleChange = event => {
this.setState({ [event.target.name]: event.target.checked });
};

handleBack = () => {
const {
history: { goBack },
} = this.props;
goBack();
};

handleSubmit = () => {
const {
userId,
location: {
state: {
space: { id, name },
},
},
dispatchExportSpace,
} = this.props;
const { space, actions, resources } = this.state;
const selection = { space, actions, resources };
dispatchExportSpace(id, name, userId, selection);
};

render() {
const {
classes,
t,
location: { state },
activity,
} = this.props;
const {
space: isSpaceChecked,
resources: isResourcesChecked,
actions: isActionsChecked,
} = this.state;

if (!state || !state.space) {
return <SpaceNotFound />;
}

if (activity) {
return (
<div className={classes.root}>
<CssBaseline />
<AppBar position="fixed">
<Toolbar />
</AppBar>
<main className="Main">
<Loader />
</main>
</div>
);
}

const spaceCheckbox = (
<Checkbox
checked={isSpaceChecked}
onChange={this.handleChange}
name="space"
color="primary"
/>
);

const resourcesCheckbox = (
<Checkbox
checked={isResourcesChecked}
onChange={this.handleChange}
name="resources"
color="primary"
/>
);
const actionsCheckbox = (
<Checkbox
checked={isActionsChecked}
onChange={this.handleChange}
name="actions"
color="primary"
/>
);

return (
<Main fullScreen>
<div>
<Typography align="center" variant="h4">
{t('What do you want to export?')}
</Typography>

<br />
<FormGroup>
<FormControlLabel control={spaceCheckbox} label={t('This Space')} />
<FormControlLabel
control={resourcesCheckbox}
label={t("This Space's User Inputs")}
/>
<FormControlLabel
control={actionsCheckbox}
label={t("This Space's Analytics")}
/>
</FormGroup>
<br />
<div className={classes.buttonGroup}>
<Button
variant="contained"
color="primary"
className={clsx(classes.button, classes.submitButton)}
onClick={this.handleBack}
>
{t('Back')}
</Button>
<Button
variant="contained"
color="primary"
className={clsx(classes.button, classes.submitButton)}
onClick={this.handleSubmit}
>
{t('Export')}
</Button>
</div>
</div>
</Main>
);
}
}

const mapStateToProps = ({ authentication, Space }) => ({
userId: authentication.getIn(['user', 'id']),
activity: Boolean(Space.getIn(['current', 'activity']).size),
});

const mapDispatchToProps = {
dispatchExportSpace: exportSpace,
};

const TranslatedComponent = withTranslation()(ExportSelectionScreen);

export default withRouter(
withStyles(styles, { withTheme: true })(
connect(mapStateToProps, mapDispatchToProps)(TranslatedComponent)
)
);
4 changes: 4 additions & 0 deletions src/config/paths.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ export const DASHBOARD_PATH = '/dashboard';
export const SIGN_IN_PATH = '/signin';
export const SYNC_SPACE_PATH = '/space/sync/:id';
export const SAVED_SPACES_PATH = '/saved-spaces';

export const buildExportSelectionPathForSpaceId = (id = ':id') => {
return `/space/export/${id}/selection`;
};

0 comments on commit 335a3a9

Please sign in to comment.