Skip to content

Commit

Permalink
Merge pull request openedx#3 from edx/djoy/shoulders_of_giants
Browse files Browse the repository at this point in the history
Absorbing work from douglashall/learning_sequence
  • Loading branch information
davidjoy authored Dec 12, 2019
2 parents 0d214bd + 32db847 commit 27db26e
Show file tree
Hide file tree
Showing 12 changed files with 186 additions and 26 deletions.
4 changes: 2 additions & 2 deletions .env.development
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
NODE_ENV='development'
PORT=8080
PORT=2000
ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload'
BASE_URL='localhost:8080'
BASE_URL='localhost:2000'
CREDENTIALS_BASE_URL='http://localhost:18150'
CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
ECOMMERCE_BASE_URL='http://localhost:18130'
Expand Down
12 changes: 0 additions & 12 deletions src/example/ExamplePage.jsx

This file was deleted.

5 changes: 0 additions & 5 deletions src/example/ExamplePage.test.jsx

This file was deleted.

Empty file removed src/example/data/.gitkeep
Empty file.
4 changes: 0 additions & 4 deletions src/example/data/README.rst

This file was deleted.

Empty file removed src/example/index.scss
Empty file.
12 changes: 10 additions & 2 deletions src/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import { APP_INIT_ERROR, APP_READY, subscribe, initialize } from '@edx/frontend-
import { AppProvider, ErrorPage } from '@edx/frontend-platform/react';
import React from 'react';
import ReactDOM from 'react-dom';
import { Route, Switch, Link } from 'react-router-dom';

import Header, { messages as headerMessages } from '@edx/frontend-component-header';
import Footer, { messages as footerMessages } from '@edx/frontend-component-footer';

import appMessages from './i18n';
import ExamplePage from './example/ExamplePage';
import LearningSequencePage from './learning-sequence/LearningSequencePage';

import './index.scss';
import './assets/favicon.ico';
Expand All @@ -18,7 +19,14 @@ subscribe(APP_READY, () => {
ReactDOM.render(
<AppProvider>
<Header />
<ExamplePage />
<Switch>
<Route
exact
path="/"
render={() => <Link to="/course/course-v1%3AedX%2BDemoX%2BDemo_Course/0">Visit Demo Course</Link>}
/>
<Route path="/course/:courseId/:blockIndex" component={LearningSequencePage} />
</Switch>
<Footer />
</AppProvider>,
document.getElementById('root'),
Expand Down
2 changes: 1 addition & 1 deletion src/index.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@import '~@edx/paragon/scss/edx/theme.scss';

@import './example/index.scss';
@import './learning-sequence/index';

@import "~@edx/frontend-component-header/dist/index";
@import "~@edx/frontend-component-footer/dist/footer";
116 changes: 116 additions & 0 deletions src/learning-sequence/LearningSequencePage.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import React, { useState, useEffect, useCallback, useRef } from 'react';
import PropTypes from 'prop-types';
import { camelCaseObject, getConfig } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';

import PageLoading from './PageLoading';

import messages from './messages';

function useApi(apiFunction, {
format = true, keepDataIfFailed = false, loadedIfFailed = false, refreshParams = [],
}) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [loaded, setLoaded] = useState(false);
const [failed, setFailed] = useState(false);
const [error, setError] = useState(null);

useEffect(() => {
setLoading(true);
apiFunction().then((response) => {
const result = format ? camelCaseObject(response.data) : response.data;
setData(result);
setLoaded(true);
setLoading(false);
setError(null);
setFailed(false);
})
.catch((e) => {
if (keepDataIfFailed) {
setData(null);
}
setFailed(true);
setLoading(false);
if (loadedIfFailed) {
setLoaded(true);
}
setError(e);
});
}, refreshParams);

return {
data,
loading,
loaded,
failed,
error,
};
}

function LearningSequencePage(props) {
const iframeRef = useRef(null);

const handleResizeIframe = useCallback(() => {
// TODO: This won't work because of crossdomain issues. Leaving here for reference once we're
// able to have the iFrame content publish resize events through postMessage
console.log('**** Resizing iframe...');
const iframe = iframeRef.current;
const contentHeight = iframe.contentWindow.document.body.scrollHeight;
console.log(`**** Height is: ${contentHeight}`);
iframe.height = contentHeight + 20;
});

const {
data,
loading,
loaded,
} = useApi(
async () => getAuthenticatedHttpClient().get(`${getConfig().LMS_BASE_URL}/api/courses/v1/blocks/?course_id=${props.match.params.courseId}&username=staff&depth=all&block_types_filter=sequential&requested_fields=children`, {}),
{
keepDataIfFailed: false,
refreshParams: [
props.match.params.courseId,
props.match.params.blockIndex,
],
},
);

console.log(data);

if (loading) {
return (
<PageLoading srMessage={props.intl.formatMessage(messages['learn.loading.learning.sequence'])} />
);
}

return (
<main>
<div className="container-fluid">
<h1>Learning Sequence Page</h1>
{loaded && data.blocks ? (
<iframe
title="yus"
ref={iframeRef}
src={Object.values(data.blocks)[parseInt(props.match.params.blockIndex, 10)].studentViewUrl}
onLoad={handleResizeIframe}
height={500}
/>
) : null}
</div>
</main>
);
}

LearningSequencePage.propTypes = {
match: PropTypes.shape({
params: PropTypes.shape({
courseId: PropTypes.string.isRequired,
blockIndex: PropTypes.number.isRequired,
}).isRequired,
}).isRequired,
intl: intlShape.isRequired,
};

export default injectIntl(LearningSequencePage);
37 changes: 37 additions & 0 deletions src/learning-sequence/PageLoading.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';

export default class PageLoading extends Component {
renderSrMessage() {
if (!this.props.srMessage) {
return null;
}

return (
<span className="sr-only">
{this.props.srMessage}
</span>
);
}

render() {
return (
<div>
<div
className="d-flex justify-content-center align-items-center flex-column"
style={{
height: '50vh',
}}
>
<div className="spinner-border text-primary" role="status">
{this.renderSrMessage()}
</div>
</div>
</div>
);
}
}

PageLoading.propTypes = {
srMessage: PropTypes.string.isRequired,
};
4 changes: 4 additions & 0 deletions src/learning-sequence/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
iframe {
border: 0;
width: 100%;
}
16 changes: 16 additions & 0 deletions src/learning-sequence/messages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { defineMessages } from '@edx/frontend-platform/i18n';

const messages = defineMessages({
'learn.loading.learning.sequence': {
id: 'learn.loading.learning.sequence',
defaultMessage: 'Loading learning sequence...',
description: 'Message when learning sequence is being loaded',
},
'learn.loading.error': {
id: 'learn.loading.error',
defaultMessage: 'Error: {error}',
description: 'Message when learning sequence fails to load',
},
});

export default messages;

0 comments on commit 27db26e

Please sign in to comment.