Further pages & resources naming and organization (#46)
* Fixing package SCSS imports. They need tildas. This ensures webpack knows to look for them in the node_modules folder, and also enables webpack resolve aliases to function (which is the mechanism that module.config.js works on) * Renaming course-page-resources to pages-and-resources It’s all course stuff in the end. * Renaming CoursePageResources to PagesAndResources To match its parent directory and the page’s user-facing name. * Simplifying name of CoursePageConfigCard to PageCard Also moving into a sub-directory where we’ll put components for the “pages” part of the UI. * Remove data README from example app. * Moving discussion-tool-selector directory Adding it as a child of the pages-and-resources module. * Organizing SCSS. * Simplifying discussions module structure. - Combining “container” and discussion tool selector into DiscussionAppList. - Removing some sub-directories that feel a bit too granular. * Splitting out some sub-components from PagesAndResources. * Removing unnecessary scss extension on import.
This commit is contained in:
@@ -4,9 +4,9 @@ import { Switch, useRouteMatch } from 'react-router';
|
||||
import { PageRoute } from '@edx/frontend-platform/react';
|
||||
|
||||
import CourseAuthoringPage from './CourseAuthoringPage';
|
||||
import { CoursePageResources } from './course-page-resources';
|
||||
import { PagesAndResources } from './pages-and-resources';
|
||||
import ProctoredExamSettings from './proctored-exam-settings/ProctoredExamSettings';
|
||||
import DiscussionToolSelectorContainer from './discussion-tool-selector/DiscussionToolSelectorContainer';
|
||||
import DiscussionAppList from './pages-and-resources/discussions/DiscussionAppList';
|
||||
|
||||
/**
|
||||
* As of this writing, these routes are mounted at a path prefixed with the following:
|
||||
@@ -28,11 +28,11 @@ export default function CourseAuthoringRoutes({ courseId }) {
|
||||
return (
|
||||
<CourseAuthoringPage courseId={courseId}>
|
||||
<Switch>
|
||||
<PageRoute exact path={`${path}/pages`}>
|
||||
<CoursePageResources courseId={courseId} />
|
||||
<PageRoute exact path={`${path}/pages-and-resources`}>
|
||||
<PagesAndResources courseId={courseId} />
|
||||
</PageRoute>
|
||||
<PageRoute path={`${path}/pages/discussion`}>
|
||||
<DiscussionToolSelectorContainer courseId={courseId} />
|
||||
<PageRoute path={`${path}/pages-and-resources/discussion`}>
|
||||
<DiscussionAppList courseId={courseId} />
|
||||
</PageRoute>
|
||||
<PageRoute path={`${path}/proctored-exam-settings`}>
|
||||
<ProctoredExamSettings courseId={courseId} />
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
data folder
|
||||
===========
|
||||
|
||||
This folder is the home for non-component files, such as redux reducers, actions, selectors, API client services, etc. See `Feature-based Application Organization <https://github.com/edx/frontend-app-course-authoring/blob/master/docs/decisions/0002-feature-based-application-organization.rst>`_. for more detail.
|
||||
@@ -1,2 +0,0 @@
|
||||
/* eslint-disable import/prefer-default-export */
|
||||
export { default as CoursePageResources } from './CoursePageResources';
|
||||
@@ -1,51 +0,0 @@
|
||||
import React from 'react';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Button, Container, Row } from '@edx/paragon';
|
||||
import PropTypes from 'prop-types';
|
||||
import DiscussionToolOption from './discussion-tool-option/DiscussionToolOption';
|
||||
import FeaturesTable from './features-table/FeaturesTable';
|
||||
import messages from '../messages';
|
||||
|
||||
function DiscussionToolSelector({
|
||||
intl, forums, featuresList, onSelectForum, selectedForumId,
|
||||
}) {
|
||||
return (
|
||||
<Container fluid className="text-info-500">
|
||||
<h6 className="my-4 text-center">{intl.formatMessage(messages.heading)}</h6>
|
||||
|
||||
<Row>
|
||||
{forums.map(forum => (
|
||||
<DiscussionToolOption
|
||||
key={forum.forumId}
|
||||
forum={forum}
|
||||
selected={forum.forumId === selectedForumId}
|
||||
onSelect={onSelectForum}
|
||||
/>
|
||||
))}
|
||||
</Row>
|
||||
|
||||
<div className="d-flex justify-content-between align-items-center">
|
||||
<h2 className="my-3">
|
||||
{intl.formatMessage(messages.supportedFeatures)}
|
||||
</h2>
|
||||
{selectedForumId && (
|
||||
<Button variant="primary">
|
||||
{intl.formatMessage(messages.configureTool, { toolName: selectedForumId })}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<FeaturesTable forums={forums} featuresList={featuresList} />
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
DiscussionToolSelector.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
forums: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
featuresList: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onSelectForum: PropTypes.func.isRequired,
|
||||
selectedForumId: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(DiscussionToolSelector);
|
||||
@@ -1,14 +1,11 @@
|
||||
@import "@edx/brand/paragon/fonts";
|
||||
@import "@edx/brand/paragon/variables";
|
||||
@import "@edx/paragon/scss/core/core";
|
||||
@import "@edx/brand/paragon/overrides";
|
||||
|
||||
@import './course-page-resources/index.scss';
|
||||
@import "~@edx/brand/paragon/fonts";
|
||||
@import "~@edx/brand/paragon/variables";
|
||||
@import "~@edx/paragon/scss/core/core";
|
||||
@import "~@edx/brand/paragon/overrides";
|
||||
|
||||
@import "studio-header/header";
|
||||
@import "~@edx/frontend-component-footer/dist/footer";
|
||||
|
||||
@import "proctored-exam-settings/proctoredExamSettings.scss";
|
||||
|
||||
@import "discussion-tool-selector/DiscussionToolSelector.scss";
|
||||
|
||||
@import "studio-header/header.scss";
|
||||
@import "pages-and-resources/index";
|
||||
@import "proctored-exam-settings/proctoredExamSettings";
|
||||
|
||||
@@ -2,13 +2,13 @@ import React, { useContext } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { AppContext } from '@edx/frontend-platform/react';
|
||||
import { Button } from '@edx/paragon';
|
||||
|
||||
import CoursePageConfigCard from './course-page/CoursePageConfigCard';
|
||||
import messages from './messages';
|
||||
import PageGrid from './pages/PageGrid';
|
||||
import ResourceList from './resources/ResourcesList';
|
||||
|
||||
// XXX this is just for testing and should be removed ASAP
|
||||
const coursePages = [
|
||||
const pages = [
|
||||
{
|
||||
id: 'cp-discussion',
|
||||
title: 'Discussion',
|
||||
@@ -65,55 +65,28 @@ const coursePages = [
|
||||
},
|
||||
];
|
||||
|
||||
function CoursePageResources({ courseId, intl }) {
|
||||
function PagesAndResources({ courseId, intl }) {
|
||||
const { config } = useContext(AppContext);
|
||||
const lmsCourseURL = `${config.LMS_BASE_URL}/courses/${courseId}`;
|
||||
return (
|
||||
<main>
|
||||
<div className="container-fluid bg-info-100">
|
||||
<div className="container-fluid bg-info-100 pb-3">
|
||||
<div className="d-flex justify-content-between align-items-center border-bottom">
|
||||
<h1 className="mt-3 text-info-500">{intl.formatMessage(messages.heading)}</h1>
|
||||
<a className="btn btn-primary" href={lmsCourseURL} role="button">
|
||||
{intl.formatMessage(messages['viewLive.button'])}
|
||||
</a>
|
||||
</div>
|
||||
<div className="text-info-500">
|
||||
<h3 className="mt-3">
|
||||
{intl.formatMessage(messages['pages.subheading'])}
|
||||
</h3>
|
||||
<div className="d-flex flex-wrap align-items-stretch justify-content-around">
|
||||
{coursePages.map((coursePage) => (
|
||||
<div
|
||||
className="d-flex flex-column align-content-stretch p-3 col-sm-12 col-md-6 col-lg-4"
|
||||
key={coursePage.id}
|
||||
>
|
||||
<CoursePageConfigCard coursePage={coursePage} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-info-500">{intl.formatMessage(messages['resources.subheading'])}</h3>
|
||||
<div className="row bg-white text-info-500 border shadow justify-content-center align-items-center my-3 mx-1">
|
||||
<div className="col-1 font-weight-bold">{intl.formatMessage(messages['resources.custom.title'])}</div>
|
||||
<div className="col-8 my-3">
|
||||
{intl.formatMessage(messages['resources.custom.description'])}
|
||||
</div>
|
||||
<div className="col-2 text-right">
|
||||
<Button variant="outline-primary">
|
||||
{intl.formatMessage(messages['resources.newPage.button'])}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<PageGrid pages={pages} />
|
||||
<ResourceList />
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
CoursePageResources.propTypes = {
|
||||
PagesAndResources.propTypes = {
|
||||
courseId: PropTypes.string.isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(CoursePageResources);
|
||||
export default injectIntl(PagesAndResources);
|
||||
@@ -1,4 +1,4 @@
|
||||
describe('coursepageresources', () => {
|
||||
describe('PagesAndResources', () => {
|
||||
it('will pass because it is an example', () => {
|
||||
|
||||
});
|
||||
@@ -1,11 +1,13 @@
|
||||
import React from 'react';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { CheckBox, Image, Col } from '@edx/paragon';
|
||||
import {
|
||||
Image, Col, Input,
|
||||
} from '@edx/paragon';
|
||||
import { faLock } from '@fortawesome/free-solid-svg-icons';
|
||||
import PropTypes from 'prop-types';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import messages from '../../messages';
|
||||
import messages from './messages';
|
||||
|
||||
function DiscussionToolOption({
|
||||
intl, forum, selected, onSelect,
|
||||
@@ -13,7 +15,7 @@ function DiscussionToolOption({
|
||||
return (
|
||||
<Col className="mb-4" xs={12} sm={6} lg={4} xl={3}>
|
||||
<div
|
||||
className="d-flex discussion-tool flex-column p-3 h-100 shadow border border-white"
|
||||
className="d-flex position-relative discussion-tool flex-column p-3 h-100 shadow border border-white"
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
@@ -23,7 +25,23 @@ function DiscussionToolOption({
|
||||
role="radio"
|
||||
aria-checked={selected}
|
||||
>
|
||||
<div className="d-flex flex-row justify-content-between">
|
||||
<div
|
||||
className="position-absolute"
|
||||
style={{
|
||||
// This positioning of 0.75rem aligns the checkbox with the top of the logo
|
||||
top: '0.75rem',
|
||||
right: '0.75rem',
|
||||
}}
|
||||
>
|
||||
{forum.isAvailable ? (
|
||||
<Input readOnly type="checkbox" checked={selected} />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faLock} />
|
||||
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="d-flex flex-row justify-content-center">
|
||||
<div className="d-flex justify-content-center">
|
||||
<Image
|
||||
height={100}
|
||||
@@ -33,13 +51,7 @@ function DiscussionToolOption({
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
<div className="d-flex">
|
||||
{forum.isAvailable ? (
|
||||
<CheckBox checked={selected} />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faLock} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<br />
|
||||
<div className="py-4">{forum.description}</div>
|
||||
@@ -1,6 +1,10 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import DiscussionToolSelector from './discussion-tool-selector/DiscussionToolSelector';
|
||||
import { Button, Container, Row } from '@edx/paragon';
|
||||
import messages from './messages';
|
||||
import DiscussionAppCard from './DiscussionAppCard';
|
||||
import FeaturesTable from './FeaturesTable';
|
||||
|
||||
// XXX this is just for testing and should be removed ASAP
|
||||
const forums = [
|
||||
@@ -40,7 +44,7 @@ const forums = [
|
||||
|
||||
const featuresList = ['LTI Integration', 'Discussion Page', 'Embedded Course Sections', 'Embedded Course Units', 'WCAG 2.1 Support'];
|
||||
|
||||
function DiscussionToolSelectorContainer({ intl }) {
|
||||
function DiscussionAppList({ intl }) {
|
||||
const [selectedForumId, setSelectedForumId] = useState(null);
|
||||
|
||||
const onSelectForum = (forumId) => {
|
||||
@@ -52,18 +56,38 @@ function DiscussionToolSelectorContainer({ intl }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<DiscussionToolSelector
|
||||
intl={intl}
|
||||
forums={forums}
|
||||
featuresList={featuresList}
|
||||
onSelectForum={onSelectForum}
|
||||
selectedForumId={selectedForumId}
|
||||
/>
|
||||
<Container fluid className="text-info-500">
|
||||
<h6 className="my-4 text-center">{intl.formatMessage(messages.heading)}</h6>
|
||||
|
||||
<Row>
|
||||
{forums.map(forum => (
|
||||
<DiscussionAppCard
|
||||
key={forum.forumId}
|
||||
forum={forum}
|
||||
selected={forum.forumId === selectedForumId}
|
||||
onSelect={onSelectForum}
|
||||
/>
|
||||
))}
|
||||
</Row>
|
||||
|
||||
<div className="d-flex justify-content-between align-items-center">
|
||||
<h2 className="my-3">
|
||||
{intl.formatMessage(messages.supportedFeatures)}
|
||||
</h2>
|
||||
{selectedForumId && (
|
||||
<Button variant="primary">
|
||||
{intl.formatMessage(messages.configureTool, { toolName: selectedForumId })}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<FeaturesTable forums={forums} featuresList={featuresList} />
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
DiscussionToolSelectorContainer.propTypes = {
|
||||
DiscussionAppList.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(DiscussionToolSelectorContainer);
|
||||
export default injectIntl(DiscussionAppList);
|
||||
@@ -1,4 +1,4 @@
|
||||
describe('CoursePageConfigCard', () => {
|
||||
describe('DiscussionAppList', () => {
|
||||
it('will pass because it is an example', () => {
|
||||
|
||||
});
|
||||
@@ -34,5 +34,5 @@ export default function FeaturesTable({ forums, featuresList }) {
|
||||
|
||||
FeaturesTable.propTypes = {
|
||||
forums: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
featuresList: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
featuresList: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
};
|
||||
2
src/pages-and-resources/index.js
Normal file
2
src/pages-and-resources/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
/* eslint-disable import/prefer-default-export */
|
||||
export { default as PagesAndResources } from './PagesAndResources';
|
||||
@@ -1,3 +1,5 @@
|
||||
@import "./discussions/DiscussionAppList";
|
||||
|
||||
.course-page-config-card {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import { faCog } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
import messages from '../messages';
|
||||
|
||||
const CoursePageShape = PropTypes.shape({
|
||||
const pageShape = PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
description: PropTypes.string.isRequired,
|
||||
@@ -19,9 +19,7 @@ const CoursePageShape = PropTypes.shape({
|
||||
showEnable: PropTypes.bool.isRequired,
|
||||
});
|
||||
|
||||
export { CoursePageShape };
|
||||
|
||||
function CoursePageConfigCard({ intl, coursePage }) {
|
||||
function PageCard({ intl, coursePage }) {
|
||||
const pageStatusMsgId = coursePage.isEnabled ? 'pageStatus.enabled' : 'pageStatus.disabled';
|
||||
const componentClasses = classNames(
|
||||
'course-page-config-card d-flex flex-column align-content-stretch',
|
||||
@@ -55,9 +53,9 @@ function CoursePageConfigCard({ intl, coursePage }) {
|
||||
);
|
||||
}
|
||||
|
||||
CoursePageConfigCard.propTypes = {
|
||||
PageCard.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
coursePage: CoursePageShape.isRequired,
|
||||
coursePage: pageShape.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(CoursePageConfigCard);
|
||||
export default injectIntl(PageCard);
|
||||
@@ -1,4 +1,4 @@
|
||||
describe('example', () => {
|
||||
describe('PageCard', () => {
|
||||
it('will pass because it is an example', () => {
|
||||
|
||||
});
|
||||
38
src/pages-and-resources/pages/PageGrid.jsx
Normal file
38
src/pages-and-resources/pages/PageGrid.jsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import PageCard from './PageCard';
|
||||
|
||||
import messages from '../messages';
|
||||
|
||||
function PageGrid({ intl, pages }) {
|
||||
return (
|
||||
<div className="text-info-500">
|
||||
<h3 className="mt-3">
|
||||
{intl.formatMessage(messages['pages.subheading'])}
|
||||
</h3>
|
||||
<div className="d-flex flex-wrap align-items-stretch justify-content-stretch">
|
||||
{pages.map((page) => (
|
||||
<PageCard key={page.id} page={page} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const pageShape = PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
isEnabled: PropTypes.bool.isRequired,
|
||||
showSettings: PropTypes.bool.isRequired,
|
||||
showStatus: PropTypes.bool.isRequired,
|
||||
showEnable: PropTypes.bool.isRequired,
|
||||
description: PropTypes.string.isRequired,
|
||||
});
|
||||
|
||||
PageGrid.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
pages: PropTypes.arrayOf(pageShape).isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(PageGrid);
|
||||
42
src/pages-and-resources/resources/ResourcesList.jsx
Normal file
42
src/pages-and-resources/resources/ResourcesList.jsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Button } from '@edx/paragon';
|
||||
|
||||
import messages from '../messages';
|
||||
|
||||
function ResourceList({ intl, resources }) {
|
||||
return (
|
||||
<div>
|
||||
<h3 className="text-info-500">{intl.formatMessage(messages['resources.subheading'])}</h3>
|
||||
<div className="row bg-white text-info-500 border shadow justify-content-center align-items-center my-3 mx-1">
|
||||
<div className="col-1 font-weight-bold">{intl.formatMessage(messages['resources.custom.title'])}</div>
|
||||
<div className="col-8 my-3">
|
||||
{intl.formatMessage(messages['resources.custom.description'])}
|
||||
</div>
|
||||
<div className="col-2 text-right">
|
||||
<Button variant="outline-primary">
|
||||
{intl.formatMessage(messages['resources.newPage.button'])}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{resources}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const resourcesShape = PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
// TODO: What is the shape of a resources model?
|
||||
});
|
||||
|
||||
ResourceList.propTypes = {
|
||||
resources: PropTypes.arrayOf(resourcesShape),
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
ResourceList.defaultProps = {
|
||||
resources: [],
|
||||
};
|
||||
|
||||
export default injectIntl(ResourceList);
|
||||
Reference in New Issue
Block a user