mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2025-02-17 08:31:23 -05:00
Merge pull request #5401 from paulkaplan/studio-recent-students
Show "students projects" filter on studio project browser
This commit is contained in:
commit
9f6f973185
5 changed files with 69 additions and 23 deletions
|
@ -127,6 +127,7 @@ module.exports.selectUsername = state => get(state, ['session', 'session', 'user
|
|||
module.exports.selectToken = state => get(state, ['session', 'session', 'user', 'token'], null);
|
||||
module.exports.selectIsAdmin = state => get(state, ['session', 'session', 'permissions', 'admin'], false);
|
||||
module.exports.selectIsSocial = state => get(state, ['session', 'session', 'permissions', 'social'], false);
|
||||
module.exports.selectIsEducator = state => get(state, ['session', 'session', 'permissions', 'educator'], false);
|
||||
|
||||
// NB logged out user id as NaN so that it can never be used in equality testing since NaN !== NaN
|
||||
module.exports.selectUserId = state => get(state, ['session', 'session', 'user', 'id'], NaN);
|
||||
|
|
|
@ -3,7 +3,7 @@ const keyMirror = require('keymirror');
|
|||
const api = require('../lib/api');
|
||||
const log = require('../lib/log');
|
||||
|
||||
const {selectUsername, selectToken} = require('./session');
|
||||
const {selectUsername, selectToken, selectIsEducator} = require('./session');
|
||||
|
||||
const Status = keyMirror({
|
||||
FETCHED: null,
|
||||
|
@ -22,6 +22,9 @@ const getInitialState = () => ({
|
|||
followers: 0,
|
||||
owner: null,
|
||||
|
||||
// BEWARE: classroomId is only loaded if the user is an educator
|
||||
classroomId: null,
|
||||
|
||||
rolesStatus: Status.NOT_FETCHED,
|
||||
manager: false,
|
||||
curator: false,
|
||||
|
@ -95,6 +98,7 @@ const selectStudioLoadFailed = state => state.studio.infoStatus === Status.ERROR
|
|||
const selectIsFetchingInfo = state => state.studio.infoStatus === Status.FETCHING;
|
||||
const selectIsFollowing = state => state.studio.following;
|
||||
const selectIsFetchingRoles = state => state.studio.rolesStatus === Status.FETCHING;
|
||||
const selectClassroomId = state => state.studio.classroomId;
|
||||
|
||||
// Thunks
|
||||
const getInfo = () => ((dispatch, getState) => {
|
||||
|
@ -138,6 +142,14 @@ const getRoles = () => ((dispatch, getState) => {
|
|||
invited: body.invited
|
||||
}));
|
||||
});
|
||||
|
||||
// Since the user is now loaded, it's a good time to check if the studio is part of a classroom
|
||||
if (selectIsEducator(state)) {
|
||||
api({uri: `/studios/${studioId}/classroom`}, (err, body, res) => {
|
||||
// No error states for inability/problems loading classroom, just swallow them
|
||||
if (!err && res.statusCode === 200 && body) dispatch(setInfo({classroomId: body.id}));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
|
@ -161,5 +173,6 @@ module.exports = {
|
|||
selectStudioLoadFailed,
|
||||
selectIsFetchingInfo,
|
||||
selectIsFetchingRoles,
|
||||
selectIsFollowing
|
||||
selectIsFollowing,
|
||||
selectClassroomId
|
||||
};
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
"studio.sharedFilter": "Shared",
|
||||
"studio.favoritedFilter": "Favorited",
|
||||
"studio.recentFilter": "Recent",
|
||||
"studio.studentsFilter": "Students",
|
||||
|
||||
"studio.activityHeader": "Activity",
|
||||
"studio.activityAddProjectToStudio": "{profileLink} added the project {projectLink}",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import keyMirror from 'keymirror';
|
||||
import api from '../../../lib/api';
|
||||
import {selectUsername} from '../../../redux/session';
|
||||
import {selectToken, selectUsername} from '../../../redux/session';
|
||||
import {selectClassroomId} from '../../../redux/studio';
|
||||
import {userProjects, projects} from './redux-modules';
|
||||
|
||||
const Errors = keyMirror({
|
||||
|
@ -12,13 +13,25 @@ const Errors = keyMirror({
|
|||
const Filters = keyMirror({
|
||||
SHARED: null,
|
||||
FAVORITED: null,
|
||||
RECENT: null
|
||||
RECENT: null,
|
||||
STUDENTS: null
|
||||
});
|
||||
|
||||
const Uris = {
|
||||
[Filters.SHARED]: username => `/users/${username}/projects`,
|
||||
[Filters.FAVORITED]: username => `/users/${username}/favorites`,
|
||||
[Filters.RECENT]: username => `/users/${username}/recent`
|
||||
const Endpoints = {
|
||||
[Filters.SHARED]: state => ({
|
||||
uri: `/users/${selectUsername(state)}/projects`
|
||||
}),
|
||||
[Filters.FAVORITED]: state => ({
|
||||
uri: `/users/${selectUsername(state)}/favorites`
|
||||
}),
|
||||
[Filters.RECENT]: state => ({
|
||||
uri: `/users/${selectUsername(state)}/projects/recentlyviewed`,
|
||||
authentication: selectToken(state)
|
||||
}),
|
||||
[Filters.STUDENTS]: state => ({
|
||||
uri: `/classrooms/${selectClassroomId(state)}/projects`,
|
||||
authentication: selectToken(state)
|
||||
})
|
||||
};
|
||||
|
||||
const normalizeError = (err, body, res) => {
|
||||
|
@ -30,14 +43,17 @@ const normalizeError = (err, body, res) => {
|
|||
|
||||
const loadUserProjects = type => ((dispatch, getState) => {
|
||||
const state = getState();
|
||||
const username = selectUsername(state);
|
||||
const projectCount = userProjects.selector(state).items.length;
|
||||
const projectsPerPage = 20;
|
||||
const opts = {
|
||||
...Endpoints[type](state),
|
||||
params: {
|
||||
limit: projectsPerPage,
|
||||
offset: projectCount
|
||||
}
|
||||
};
|
||||
dispatch(userProjects.actions.loading());
|
||||
api({
|
||||
uri: Uris[type](username),
|
||||
params: {limit: projectsPerPage, offset: projectCount}
|
||||
}, (err, body, res) => {
|
||||
api(opts, (err, body, res) => {
|
||||
const error = normalizeError(err, body, res);
|
||||
if (error) return dispatch(userProjects.actions.error(error));
|
||||
const moreToLoad = body.length === projectsPerPage;
|
||||
|
|
|
@ -5,6 +5,7 @@ import {connect} from 'react-redux';
|
|||
import classNames from 'classnames';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import {selectClassroomId} from '../../../redux/studio';
|
||||
import {addProject, removeProject} from '../lib/studio-project-actions';
|
||||
import {userProjects} from '../lib/redux-modules';
|
||||
import {Filters, loadUserProjects, clearUserProjects} from '../lib/user-projects-actions';
|
||||
|
@ -16,10 +17,11 @@ import SubNavigation from '../../../components/subnavigation/subnavigation.jsx';
|
|||
import UserProjectsTile from './user-projects-tile.jsx';
|
||||
|
||||
import './user-projects-modal.scss';
|
||||
import {selectIsEducator} from '../../../redux/session';
|
||||
|
||||
const UserProjectsModal = ({
|
||||
items, error, loading, moreToLoad, onLoadMore, onClear,
|
||||
onAdd, onRemove, onRequestClose
|
||||
items, error, loading, moreToLoad, showStudentsFilter,
|
||||
onLoadMore, onClear, onAdd, onRemove, onRequestClose
|
||||
}) => {
|
||||
const [filter, setFilter] = useState(Filters.SHARED);
|
||||
|
||||
|
@ -60,6 +62,14 @@ const UserProjectsModal = ({
|
|||
>
|
||||
<FormattedMessage id="studio.recentFilter" />
|
||||
</li>
|
||||
{showStudentsFilter &&
|
||||
<li
|
||||
className={classNames({active: filter === Filters.STUDENTS})}
|
||||
onClick={() => setFilter(Filters.STUDENTS)}
|
||||
>
|
||||
<FormattedMessage id="studio.studentsFilter" />
|
||||
</li>
|
||||
}
|
||||
</SubNavigation>
|
||||
<ModalInnerContent className="user-projects-modal-content">
|
||||
{error && <div>Error loading {filter}: {error}</div>}
|
||||
|
@ -75,15 +85,18 @@ const UserProjectsModal = ({
|
|||
onRemove={onRemove}
|
||||
/>
|
||||
))}
|
||||
{moreToLoad &&
|
||||
<div className="studio-projects-load-more">
|
||||
{loading ? <small>Loading...</small> : (
|
||||
moreToLoad ?
|
||||
<button onClick={() => onLoadMore(filter)}>
|
||||
<FormattedMessage id="general.loadMore" />
|
||||
</button> :
|
||||
<small>No more to load</small>
|
||||
)}
|
||||
<button
|
||||
className={classNames('button', {
|
||||
'mod-mutating': loading
|
||||
})}
|
||||
onClick={onLoadMore}
|
||||
>
|
||||
<FormattedMessage id="general.loadMore" />
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</ModalInnerContent>
|
||||
</Modal>
|
||||
|
@ -91,6 +104,7 @@ const UserProjectsModal = ({
|
|||
};
|
||||
|
||||
UserProjectsModal.propTypes = {
|
||||
showStudentsFilter: PropTypes.bool,
|
||||
items: PropTypes.arrayOf(PropTypes.shape({
|
||||
id: PropTypes.id,
|
||||
image: PropTypes.string,
|
||||
|
@ -108,7 +122,8 @@ UserProjectsModal.propTypes = {
|
|||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
...userProjects.selector(state)
|
||||
...userProjects.selector(state),
|
||||
showStudentsFilter: selectIsEducator(state) && selectClassroomId(state)
|
||||
});
|
||||
|
||||
const mapDispatchToProps = ({
|
||||
|
|
Loading…
Reference in a new issue