Skip to content

Commit f4a974b

Browse files
committed
feat: components in collections
1 parent c0aab46 commit f4a974b

File tree

11 files changed

+130
-82
lines changed

11 files changed

+130
-82
lines changed

src/library-authoring/EmptyStates.tsx

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React, { useContext, useCallback } from 'react';
22
import { useParams } from 'react-router';
33
import { FormattedMessage } from '@edx/frontend-platform/i18n';
4+
import type { MessageDescriptor } from 'react-intl';
45
import {
56
Button, Stack,
67
} from '@openedx/paragon';
@@ -10,11 +11,13 @@ import messages from './messages';
1011
import { LibraryContext } from './common/context';
1112
import { useContentLibrary } from './data/apiHooks';
1213

13-
type NoSearchResultsProps = {
14-
searchType?: 'collection' | 'component',
15-
};
16-
17-
export const NoComponents = ({ searchType = 'component' }: NoSearchResultsProps) => {
14+
export const NoComponents = ({
15+
infoText = messages.noComponents,
16+
addBtnText = messages.addComponent,
17+
}: {
18+
infoText: MessageDescriptor;
19+
addBtnText: MessageDescriptor;
20+
}) => {
1821
const { openAddContentSidebar, openCreateCollectionModal } = useContext(LibraryContext);
1922
const { libraryId } = useParams();
2023
const { data: libraryData } = useContentLibrary(libraryId);
@@ -30,25 +33,23 @@ export const NoComponents = ({ searchType = 'component' }: NoSearchResultsProps)
3033

3134
return (
3235
<Stack direction="horizontal" gap={3} className="mt-6 justify-content-center">
33-
{searchType === 'collection'
34-
? <FormattedMessage {...messages.noCollections} />
35-
: <FormattedMessage {...messages.noComponents} />}
36+
<FormattedMessage {...infoText} />
3637
{canEditLibrary && (
3738
<Button iconBefore={Add} onClick={handleOnClickButton}>
38-
{searchType === 'collection'
39-
? <FormattedMessage {...messages.addCollection} />
40-
: <FormattedMessage {...messages.addComponent} />}
39+
<FormattedMessage {...addBtnText} />
4140
</Button>
4241
)}
4342
</Stack>
4443
);
4544
};
4645

47-
export const NoSearchResults = ({ searchType = 'component' }: NoSearchResultsProps) => (
46+
export const NoSearchResults = ({
47+
infoText = messages.noSearchResults,
48+
}: {
49+
infoText: MessageDescriptor;
50+
}) => (
4851
<Stack direction="horizontal" gap={3} className="my-6 justify-content-center">
49-
{searchType === 'collection'
50-
? <FormattedMessage {...messages.noSearchResultsCollections} />
51-
: <FormattedMessage {...messages.noSearchResults} />}
52+
<FormattedMessage {...infoText} />
5253
<ClearFiltersButton variant="primary" size="md" />
5354
</Stack>
5455
);

src/library-authoring/LibraryAuthoringPage.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
}
1212

1313
.library-authoring-sidebar {
14-
min-width: 300px;
14+
min-width: 320px;
1515
max-width: map-get($grid-breakpoints, "sm");
1616
z-index: 1001; // to appear over header
1717
position: sticky;

src/library-authoring/collections/CollectionInfo.tsx

Lines changed: 13 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,27 @@
1-
import React from 'react';
21
import { useIntl } from '@edx/frontend-platform/i18n';
32
import {
43
Tab,
54
Tabs,
6-
Stack,
75
} from '@openedx/paragon';
86

97
import messages from './messages';
10-
import { useCollection } from '../data/apiHooks';
11-
import Loading from '../../generic/Loading';
128

13-
interface CollectionInfoProps {
14-
collectionId: string;
15-
libraryId: string;
16-
}
17-
18-
const CollectionInfo = ({ libraryId, collectionId } : CollectionInfoProps) => {
9+
const CollectionInfo = () => {
1910
const intl = useIntl();
20-
const { data: collectionData, isLoading: isCollectionLoading } = useCollection(libraryId, collectionId);
21-
22-
if (isCollectionLoading) {
23-
return <Loading />;
24-
}
2511

2612
return (
27-
<Stack>
28-
<div className="d-flex flex-wrap">
29-
{collectionData?.title}
30-
</div>
31-
<Tabs
32-
variant="tabs"
33-
className="my-3 d-flex justify-content-around"
34-
defaultActiveKey="manage"
35-
>
36-
<Tab eventKey="manage" title={intl.formatMessage(messages.manageTabTitle)}>
37-
Manage tab placeholder
38-
</Tab>
39-
<Tab eventKey="details" title={intl.formatMessage(messages.detailsTabTitle)}>
40-
Details tab placeholder
41-
</Tab>
42-
</Tabs>
43-
</Stack>
13+
<Tabs
14+
variant="tabs"
15+
className="my-3 d-flex justify-content-around"
16+
defaultActiveKey="manage"
17+
>
18+
<Tab eventKey="manage" title={intl.formatMessage(messages.manageTabTitle)}>
19+
Manage tab placeholder
20+
</Tab>
21+
<Tab eventKey="details" title={intl.formatMessage(messages.detailsTabTitle)}>
22+
Details tab placeholder
23+
</Tab>
24+
</Tabs>
4425
);
4526
};
4627

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { Collection } from '../data/api';
2+
3+
interface CollectionInfoHeaderProps {
4+
collection?: Collection;
5+
}
6+
7+
const CollectionInfoHeader = ({ collection } : CollectionInfoHeaderProps) => {
8+
return (
9+
<div className="d-flex flex-wrap">
10+
{collection?.title}
11+
</div>
12+
);
13+
};
14+
15+
export default CollectionInfoHeader;
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Stack } from '@openedx/paragon';
2+
import { NoComponents, NoSearchResults } from '../EmptyStates';
3+
import { useSearchContext } from '../../search-manager';
4+
import { LibraryComponents } from '../components';
5+
import messages from './messages';
6+
7+
const LibraryCollectionComponents = ({ libraryId }: { libraryId: string }) => {
8+
const { totalHits: componentCount, isFiltered } = useSearchContext();
9+
10+
if (componentCount === 0) {
11+
return isFiltered ?
12+
<NoSearchResults infoText={messages.noSearchResultsInCollection} />
13+
: <NoComponents infoText={messages.noComponentsInCollection} addBtnText={messages.addComponentsInCollection} />;
14+
}
15+
16+
return (
17+
<Stack direction="vertical" gap={3}>
18+
<h3 className="text-gray">Content ({componentCount})</h3>
19+
<LibraryComponents libraryId={libraryId} variant="full" />
20+
</Stack>
21+
);
22+
};
23+
24+
export default LibraryCollectionComponents;

src/library-authoring/collections/LibraryCollectionPage.tsx

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useContext, useEffect } from 'react';
1+
import { useContext, useEffect } from 'react';
22
import { StudioFooter } from '@edx/frontend-component-footer';
33
import { useIntl } from '@edx/frontend-platform/i18n';
44
import {
@@ -8,6 +8,7 @@ import {
88
Container,
99
Icon,
1010
IconButton,
11+
Row,
1112
Stack,
1213
} from '@openedx/paragon';
1314
import { Add, InfoOutline } from '@openedx/paragon/icons';
@@ -29,12 +30,9 @@ import { useCollection, useContentLibrary } from '../data/apiHooks';
2930
import { LibraryContext } from '../common/context';
3031
import messages from '../messages';
3132
import { LibrarySidebar } from '../library-sidebar';
33+
import LibraryCollectionComponents from './LibraryCollectionComponents';
3234

33-
interface HeaderActionsProps {
34-
canEditLibrary: boolean;
35-
}
36-
37-
const HeaderActions = ({ canEditLibrary }: HeaderActionsProps) => {
35+
const HeaderActions = ({ canEditLibrary }: { canEditLibrary: boolean; }) => {
3836
const intl = useIntl();
3937
const {
4038
openAddContentSidebar,
@@ -59,7 +57,15 @@ const HeaderActions = ({ canEditLibrary }: HeaderActionsProps) => {
5957
);
6058
};
6159

62-
const SubHeaderTitle = ({ title, canEditLibrary, infoClickHandler }: { title: string, canEditLibrary: boolean, infoClickHandler: () => void }) => {
60+
const SubHeaderTitle = ({
61+
title,
62+
canEditLibrary,
63+
infoClickHandler,
64+
}: {
65+
title: string;
66+
canEditLibrary: boolean;
67+
infoClickHandler: () => void;
68+
}) => {
6369
const intl = useIntl();
6470

6571
return (
@@ -85,7 +91,13 @@ const SubHeaderTitle = ({ title, canEditLibrary, infoClickHandler }: { title: st
8591
);
8692
};
8793

88-
const LibraryCollectionPage = ({ libraryId, collectionId }: { libraryId: string, collectionId: string }) => {
94+
const LibraryCollectionPage = ({
95+
libraryId,
96+
collectionId,
97+
}: {
98+
libraryId: string;
99+
collectionId: string;
100+
}) => {
89101
const intl = useIntl();
90102

91103
const {
@@ -94,7 +106,7 @@ const LibraryCollectionPage = ({ libraryId, collectionId }: { libraryId: string,
94106
} = useContext(LibraryContext);
95107

96108
useEffect(() => {
97-
openCollectionInfoSidebar(collectionId);
109+
openCollectionInfoSidebar();
98110
}, []);
99111

100112
const { data: libraryData, isLoading: isLibLoading } = useContentLibrary(libraryId);
@@ -139,7 +151,7 @@ const LibraryCollectionPage = ({ libraryId, collectionId }: { libraryId: string,
139151
title={<SubHeaderTitle
140152
title={collectionData.title}
141153
canEditLibrary={libraryData.canEditLibrary}
142-
infoClickHandler={() => openCollectionInfoSidebar(collectionId)}
154+
infoClickHandler={openCollectionInfoSidebar}
143155
/>}
144156
breadcrumbs={(
145157
<Breadcrumb
@@ -151,19 +163,20 @@ const LibraryCollectionPage = ({ libraryId, collectionId }: { libraryId: string,
151163
headerActions={<HeaderActions canEditLibrary={libraryData.canEditLibrary} />}
152164
/>
153165
<SearchKeywordsField className="w-50" />
154-
<div className="d-flex mt-3 align-items-center">
166+
<div className="d-flex mt-3 mb-4 align-items-center">
155167
<FilterByTags />
156168
<FilterByBlockType />
157169
<ClearFiltersButton />
158170
<div className="flex-grow-1" />
159171
<SearchSortWidget />
160172
</div>
173+
<LibraryCollectionComponents libraryId={libraryId} />
161174
</Container>
162175
<StudioFooter />
163176
</div>
164177
{ !!sidebarBodyComponent && (
165178
<div className="library-authoring-sidebar box-shadow-left-1 bg-white" data-testid="library-sidebar">
166-
<LibrarySidebar library={libraryData} />
179+
<LibrarySidebar library={libraryData} collection={collectionData} />
167180
</div>
168181
)}
169182
</div>
@@ -178,7 +191,8 @@ const LibraryCollectionPageWrapper = () => {
178191

179192
return (
180193
<SearchContextProvider
181-
extraFilter={`context_key = "${libraryId}"`}
194+
extraFilter={[`context_key = "${libraryId}"`, `collections.key = "${collectionId}"`]}
195+
fetchCollections={false}
182196
>
183197
<LibraryCollectionPage libraryId={libraryId} collectionId={collectionId} />
184198
</SearchContextProvider>

src/library-authoring/collections/LibraryCollections.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { useSearchContext } from '../../search-manager';
55
import { NoComponents, NoSearchResults } from '../EmptyStates';
66
import CollectionCard from '../components/CollectionCard';
77
import { LIBRARY_SECTION_PREVIEW_LIMIT } from '../components/LibrarySection';
8+
import messages from '../messages';
89

910
type LibraryCollectionsProps = {
1011
variant: 'full' | 'preview',
@@ -37,7 +38,9 @@ const LibraryCollections = ({ variant }: LibraryCollectionsProps) => {
3738
);
3839

3940
if (totalCollectionHits === 0) {
40-
return isFiltered ? <NoSearchResults searchType="collection" /> : <NoComponents searchType="collection" />;
41+
return isFiltered ?
42+
<NoSearchResults infoText={messages.noSearchResultsCollections} />
43+
: <NoComponents infoText={messages.noCollections} addBtnText={messages.addCollection} />;
4144
}
4245

4346
return (
@@ -50,12 +53,12 @@ const LibraryCollections = ({ variant }: LibraryCollectionsProps) => {
5053
}}
5154
hasEqualColumnHeights
5255
>
53-
{ collectionList.map((collectionHit) => (
56+
{collectionList.map((collectionHit) => (
5457
<CollectionCard
5558
key={collectionHit.id}
5659
collectionHit={collectionHit}
5760
/>
58-
)) }
61+
))}
5962
</CardGrid>
6063
);
6164
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export { default as CollectionInfo } from './CollectionInfo';
2+
export { default as CollectionInfoHeader } from './CollectionInfoHeader';

src/library-authoring/collections/messages.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,21 @@ const messages = defineMessages({
1111
defaultMessage: 'Details',
1212
description: 'Title for details tab',
1313
},
14+
noComponentsInCollection: {
15+
id: 'course-authoring.library-authoring.collections-pag.no-components.text',
16+
defaultMessage: 'This collection is currently empty.',
17+
description: 'Text to display when collection has no associated components',
18+
},
19+
addComponentsInCollection: {
20+
id: 'course-authoring.library-authoring.collections-pag.add-components.btn-text',
21+
defaultMessage: 'New',
22+
description: 'Text to display in new button if no components in collection is found',
23+
},
24+
noSearchResultsInCollection: {
25+
id: 'course-authoring.library-authoring.collections-pag.no-search-results.text',
26+
defaultMessage: 'No matching components found in this collections.',
27+
description: 'Message displayed when no matching components are found in collection',
28+
},
1429
});
1530

1631
export default messages;

src/library-authoring/common/context.tsx

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export interface LibraryContextData {
1818
isCreateCollectionModalOpen: boolean;
1919
openCreateCollectionModal: () => void;
2020
closeCreateCollectionModal: () => void;
21-
openCollectionInfoSidebar: (collectionId: string) => void;
21+
openCollectionInfoSidebar: () => void;
2222
}
2323

2424
export const LibraryContext = React.createContext({
@@ -30,7 +30,7 @@ export const LibraryContext = React.createContext({
3030
isCreateCollectionModalOpen: false,
3131
openCreateCollectionModal: () => {},
3232
closeCreateCollectionModal: () => {},
33-
openCollectionInfoSidebar: (_collectionId: string) => {}, // eslint-disable-line @typescript-eslint/no-unused-vars
33+
openCollectionInfoSidebar: () => {}, // eslint-disable-line @typescript-eslint/no-unused-vars
3434
} as LibraryContextData);
3535

3636
/**
@@ -61,13 +61,10 @@ export const LibraryProvider = (props: { children?: React.ReactNode }) => {
6161
},
6262
[],
6363
);
64-
const openCollectionInfoSidebar = React.useCallback(
65-
(collectionId: string) => {
66-
setCurrentComponentKey(collectionId);
67-
setSidebarBodyComponent(SidebarBodyComponentId.CollectionInfo);
68-
},
69-
[],
70-
);
64+
const openCollectionInfoSidebar = React.useCallback(() => {
65+
setCurrentComponentKey(undefined);
66+
setSidebarBodyComponent(SidebarBodyComponentId.CollectionInfo);
67+
}, []);
7168

7269
const context = React.useMemo(() => ({
7370
sidebarBodyComponent,

0 commit comments

Comments
 (0)