Skip to content

Commit

Permalink
feat: library home page bare bones
Browse files Browse the repository at this point in the history
  • Loading branch information
rpenido committed May 31, 2024
1 parent 049e490 commit 1f08c0a
Show file tree
Hide file tree
Showing 10 changed files with 231 additions and 56 deletions.
33 changes: 5 additions & 28 deletions src/CourseAuthoringPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,6 @@ import { getCourseAppsApiStatus } from './pages-and-resources/data/selectors';
import { RequestStatus } from './data/constants';
import Loading from './generic/Loading';

const AppHeader = ({
courseNumber, courseOrg, courseTitle, courseId,
}) => (
<Header
courseNumber={courseNumber}
courseOrg={courseOrg}
courseTitle={courseTitle}
courseId={courseId}
/>
);

AppHeader.propTypes = {
courseId: PropTypes.string.isRequired,
courseNumber: PropTypes.string,
courseOrg: PropTypes.string,
courseTitle: PropTypes.string.isRequired,
};

AppHeader.defaultProps = {
courseNumber: null,
courseOrg: null,
};

const CourseAuthoringPage = ({ courseId, children }) => {
const dispatch = useDispatch();

Expand Down Expand Up @@ -74,11 +51,11 @@ const CourseAuthoringPage = ({ courseId, children }) => {
This functionality will be removed in TNL-9591 */}
{inProgress ? !isEditor && <Loading />
: (!isEditor && (
<AppHeader
courseNumber={courseNumber}
courseOrg={courseOrg}
courseTitle={courseTitle}
courseId={courseId}
<Header
number={courseNumber}
org={courseOrg}
title={courseTitle}
contentId={courseId}
/>
)
)}
Expand Down
51 changes: 27 additions & 24 deletions src/header/Header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,57 +6,58 @@ import { useIntl } from '@edx/frontend-platform/i18n';
import { StudioHeader } from '@edx/frontend-component-header';
import { useToggle } from '@openedx/paragon';

import SearchModal from '../search-modal/SearchModal';
import { SearchModal } from '../search-modal';

Check failure on line 9 in src/header/Header.jsx

View workflow job for this annotation

GitHub Actions / tests

Missing file extension "ts" for "../search-modal"
import { getContentMenuItems, getSettingMenuItems, getToolsMenuItems } from './utils';
import messages from './messages';

const Header = ({
courseId,
courseOrg,
courseNumber,
courseTitle,
contentId,
org,
number,
title,
isHiddenMainMenu,
isLibrary,
}) => {
const intl = useIntl();

const [isShowSearchModalOpen, openSearchModal, closeSearchModal] = useToggle(false);

const studioBaseUrl = getConfig().STUDIO_BASE_URL;
const meiliSearchEnabled = [true, 'true'].includes(getConfig().MEILISEARCH_ENABLED);
const mainMenuDropdowns = [
const mainMenuDropdowns = !isLibrary ? [
{
id: `${intl.formatMessage(messages['header.links.content'])}-dropdown-menu`,
buttonTitle: intl.formatMessage(messages['header.links.content']),
items: getContentMenuItems({ studioBaseUrl, courseId, intl }),
items: getContentMenuItems({ studioBaseUrl, courseId: contentId, intl }),
},
{
id: `${intl.formatMessage(messages['header.links.settings'])}-dropdown-menu`,
buttonTitle: intl.formatMessage(messages['header.links.settings']),
items: getSettingMenuItems({ studioBaseUrl, courseId, intl }),
items: getSettingMenuItems({ studioBaseUrl, courseId: contentId, intl }),
},
{
id: `${intl.formatMessage(messages['header.links.tools'])}-dropdown-menu`,
buttonTitle: intl.formatMessage(messages['header.links.tools']),
items: getToolsMenuItems({ studioBaseUrl, courseId, intl }),
items: getToolsMenuItems({ studioBaseUrl, courseId: contentId, intl }),
},
];
const outlineLink = `${studioBaseUrl}/course/${courseId}`;
] : [];
const outlineLink = !isLibrary ? `${studioBaseUrl}/course/${contentId}` : `${studioBaseUrl}/library/${contentId}`;

return (
<>
<StudioHeader
org={courseOrg}
number={courseNumber}
title={courseTitle}
org={org}
number={number}
title={title}
isHiddenMainMenu={isHiddenMainMenu}
mainMenuDropdowns={mainMenuDropdowns}
outlineLink={outlineLink}
searchButtonAction={meiliSearchEnabled && openSearchModal}
searchButtonAction={meiliSearchEnabled && !isLibrary ? openSearchModal : undefined}
/>
{ meiliSearchEnabled && (
<SearchModal
isOpen={isShowSearchModalOpen}
courseId={courseId}
courseId={isLibrary ? undefined : contentId}
onClose={closeSearchModal}
/>
)}
Expand All @@ -65,19 +66,21 @@ const Header = ({
};

Header.propTypes = {
courseId: PropTypes.string,
courseNumber: PropTypes.string,
courseOrg: PropTypes.string,
courseTitle: PropTypes.string,
contentId: PropTypes.string,
number: PropTypes.string,
org: PropTypes.string,
title: PropTypes.string,
isHiddenMainMenu: PropTypes.bool,
isLibrary: PropTypes.bool,
};

Header.defaultProps = {
courseId: '',
courseNumber: '',
courseOrg: '',
courseTitle: '',
contentId: '',
number: '',
org: '',
title: '',
isHiddenMainMenu: false,
isLibrary: false,
};

export default Header;
4 changes: 2 additions & 2 deletions src/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ import { initializeHotjar } from '@edx/frontend-enterprise-hotjar';
import { logError } from '@edx/frontend-platform/logging';
import messages from './i18n';

import { LibraryAuthoringPage } from './library-authoring';

Check failure on line 22 in src/index.jsx

View workflow job for this annotation

GitHub Actions / tests

Missing file extension "ts" for "./library-authoring"
import initializeStore from './store';
import CourseAuthoringRoutes from './CourseAuthoringRoutes';
import Head from './head/Head';
import { StudioHome } from './studio-home';
import LibraryV2Placeholder from './studio-home/tabs-section/LibraryV2Placeholder.tsx';
import CourseRerun from './course-rerun';
import { TaxonomyLayout, TaxonomyDetailPage, TaxonomyListPage } from './taxonomy';
import { ContentTagsDrawer } from './content-tags-drawer';
Expand Down Expand Up @@ -55,7 +55,7 @@ const App = () => {
<Route path="/home" element={<StudioHome />} />
<Route path="/libraries" element={<StudioHome />} />
<Route path="/legacy-libraries" element={<StudioHome />} />
<Route path="/library/:libraryId" element={<LibraryV2Placeholder />} />
<Route path="/library/:libraryId/*" element={<LibraryAuthoringPage />} />
<Route path="/course/:courseId/*" element={<CourseAuthoringRoutes />} />
<Route path="/course_rerun/:courseId" element={<CourseRerun />} />
{getConfig().ENABLE_ACCESSIBILITY_PAGE === 'true' && (
Expand Down
92 changes: 92 additions & 0 deletions src/library-authoring/LibraryAuthoringPage.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// @ts-check
/* eslint-disable react/prop-types */
import React, { useEffect } from 'react';
import { StudioFooter } from '@edx/frontend-component-footer';
import { Tab, Tabs } from '@openedx/paragon';
import {
Routes, Route, useLocation, useNavigate,
} from 'react-router-dom';

import Header from '../header';
import NotFoundAlert from '../generic/NotFoundAlert';
import LibraryComponents from './LibraryComponents';
import LibraryCollections from './LibraryCollections';
import LibraryHome from './LibraryHome';

const TAB_LIST = {
home: '',
components: 'components',
collections: 'collections',
};

/**
* @type {React.FC}
*/
const LibraryAuthoringPage = () => {
const location = useLocation();
const navigate = useNavigate();
const [tabKey, setTabKey] = React.useState(TAB_LIST.home);

// FIXME: These values should be fetched from the backend
const libraryTitle = 'Library Title';
const libraryNumber = '001';
const libraryOrg = 'Library Org';
const libraryId = 'lib:org1:libafter1';

useEffect(() => {
const currentPath = location.pathname.split('/').pop();
if (currentPath && Object.values(TAB_LIST).includes(currentPath)) {
setTabKey(currentPath);
}
}, [location]);

/** Handle tab change
* @param {string} key
*/
const handleTabChange = (key) => {
setTabKey(key);
navigate(key);
};

return (
<div className="bg-white">
<Header
number={libraryNumber}
title={libraryTitle}
org={libraryOrg}
contentId={libraryId}
isLibrary
/>
<Tabs
variant="tabs"
activeKey={tabKey}
onSelect={handleTabChange}
>
<Tab eventKey={TAB_LIST.home} title="Home" />
<Tab eventKey={TAB_LIST.components} title="Components" />
<Tab eventKey={TAB_LIST.collections} title="Collections" />
</Tabs>
<Routes>
<Route
path={TAB_LIST.home}
element={<LibraryHome libraryId={libraryId} />}
/>
<Route
path={TAB_LIST.components}
element={<LibraryComponents />}
/>
<Route
path={TAB_LIST.collections}
element={<LibraryCollections />}
/>
<Route
path="*"
element={<NotFoundAlert />}
/>
</Routes>
<StudioFooter />
</div>
);
};

export default LibraryAuthoringPage;
14 changes: 14 additions & 0 deletions src/library-authoring/LibraryCollections.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// @ts-check
/* eslint-disable react/prop-types */
import React from 'react';

/**
* @type {React.FC}
*/
const LibraryCollections = () => (
<div>
Coming soon
</div>
);

export default LibraryCollections;
14 changes: 14 additions & 0 deletions src/library-authoring/LibraryComponents.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// @ts-check
/* eslint-disable react/prop-types */
import React from 'react';

/**
* @type {React.FC}
*/
const LibraryComponents = () => (
<div>
Library components will be displayed here.
</div>
);

export default LibraryComponents;
66 changes: 66 additions & 0 deletions src/library-authoring/LibraryHome.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// @ts-check
/* eslint-disable react/prop-types */
import React from 'react';
import { MeiliSearch } from 'meilisearch';

import { useContentSearchConnection, useContentSearchResults } from '../search-modal';

Check failure on line 6 in src/library-authoring/LibraryHome.jsx

View workflow job for this annotation

GitHub Actions / tests

Missing file extension "ts" for "../search-modal"
import LibraryCollections from './LibraryCollections';
import LibraryComponents from './LibraryComponents';

/**
* @type {React.FC<{
* title: string,
* children: React.ReactNode,
* }>}
*/
const Section = ({ title, children }) => (
<div className="bg-gray-100 mx-3 mt-3 mb-4 p-3">
<h2>{title}</h2>
{children}
</div>
);

/**
* @type {React.FC<{
* libraryId: string,
* }>}
*/
const LibraryHome = ({ libraryId }) => {
// Meilisearch code to get Collection and Component counts
const { data: connectionDetails } = useContentSearchConnection();
const indexName = connectionDetails?.indexName;
const client = React.useMemo(() => {
if (connectionDetails?.apiKey === undefined || connectionDetails?.url === undefined) {
return undefined;
}
return new MeiliSearch({ host: connectionDetails.url, apiKey: connectionDetails.apiKey });
}, [connectionDetails?.apiKey, connectionDetails?.url]);

const searchKeywords = '';
const libFilter = `context_key = "${libraryId}"`;

const { totalHits: componentCount } = useContentSearchResults({
client,
indexName,
searchKeywords,
extraFilter: [libFilter], // ToDo: Add filter for components when collection is implemented
});

const collectionsCount = 0; // ToDo: Implement collections count

return (
<>
<Section title="Recently Modified">
Recently modified components and collections will be displayed here.
</Section>
<Section title={`Collections (${collectionsCount})`}>
<LibraryCollections />
</Section>
<Section title={`Components (${componentCount})`}>
<LibraryComponents />
</Section>
</>
);
};

export default LibraryHome;
3 changes: 3 additions & 0 deletions src/library-authoring/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// @ts-check
// eslint-disable-next-line import/prefer-default-export
export { default as LibraryAuthoringPage } from './LibraryAuthoringPage';
7 changes: 5 additions & 2 deletions src/search-modal/data/apiHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ export const useContentSearchConnection = () => (
* @param {string} [context.indexName] Which search index contains the content data
* @param {import('meilisearch').Filter} [context.extraFilter] Other filters to apply to the search, e.g. course ID
* @param {string} context.searchKeywords The keywords that the user is searching for, if any
* @param {string[]} context.blockTypesFilter Only search for these block types (e.g. ["html", "problem"])
* @param {string[]} context.tagsFilter Required tags (all must match), e.g. ["Difficulty > Hard", "Subject > Math"]
* @param {string[]} [context.blockTypesFilter] Only search for these block types (e.g. ["html", "problem"])
* @param {string[]} [context.tagsFilter] Required tags (all must match), e.g. ["Difficulty > Hard", "Subject > Math"]
*/
export const useContentSearchResults = ({
client,
Expand All @@ -45,6 +45,9 @@ export const useContentSearchResults = ({
blockTypesFilter,
tagsFilter,
}) => {
blockTypesFilter ??= []; // eslint-disable-line no-param-reassign -- default value for optional parameter
tagsFilter ??= []; // eslint-disable-line no-param-reassign -- Default value for optional parameter

const query = useInfiniteQuery({
enabled: client !== undefined && indexName !== undefined,
queryKey: [
Expand Down
3 changes: 3 additions & 0 deletions src/search-modal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// @ts-check
export { default as SearchModal } from './SearchModal';
export { useContentSearchConnection, useContentSearchResults } from './data/apiHooks';

0 comments on commit 1f08c0a

Please sign in to comment.