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:
David Joy
2021-01-11 12:05:03 -05:00
committed by GitHub
parent 62c7e49c3d
commit cb11c1dd01
21 changed files with 173 additions and 142 deletions

View File

@@ -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} />

View File

@@ -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.

View File

@@ -1,2 +0,0 @@
/* eslint-disable import/prefer-default-export */
export { default as CoursePageResources } from './CoursePageResources';

View File

@@ -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);

View File

@@ -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";

View File

@@ -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);

View File

@@ -1,4 +1,4 @@
describe('coursepageresources', () => {
describe('PagesAndResources', () => {
it('will pass because it is an example', () => {
});

View File

@@ -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>

View File

@@ -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);

View File

@@ -1,4 +1,4 @@
describe('CoursePageConfigCard', () => {
describe('DiscussionAppList', () => {
it('will pass because it is an example', () => {
});

View File

@@ -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,
};

View File

@@ -0,0 +1,2 @@
/* eslint-disable import/prefer-default-export */
export { default as PagesAndResources } from './PagesAndResources';

View File

@@ -1,3 +1,5 @@
@import "./discussions/DiscussionAppList";
.course-page-config-card {
flex-basis: 100%;
}

View File

@@ -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);

View File

@@ -1,4 +1,4 @@
describe('example', () => {
describe('PageCard', () => {
it('will pass because it is an example', () => {
});

View 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);

View 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);