Skip to content

Commit

Permalink
feat: components in collections
Browse files Browse the repository at this point in the history
  • Loading branch information
navinkarkera committed Sep 14, 2024
1 parent c0aab46 commit f4a974b
Show file tree
Hide file tree
Showing 11 changed files with 130 additions and 82 deletions.
31 changes: 16 additions & 15 deletions src/library-authoring/EmptyStates.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useContext, useCallback } from 'react';
import { useParams } from 'react-router';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import type { MessageDescriptor } from 'react-intl';
import {
Button, Stack,
} from '@openedx/paragon';
Expand All @@ -10,11 +11,13 @@ import messages from './messages';
import { LibraryContext } from './common/context';
import { useContentLibrary } from './data/apiHooks';

type NoSearchResultsProps = {
searchType?: 'collection' | 'component',
};

export const NoComponents = ({ searchType = 'component' }: NoSearchResultsProps) => {
export const NoComponents = ({
infoText = messages.noComponents,
addBtnText = messages.addComponent,
}: {
infoText: MessageDescriptor;
addBtnText: MessageDescriptor;
}) => {
const { openAddContentSidebar, openCreateCollectionModal } = useContext(LibraryContext);
const { libraryId } = useParams();
const { data: libraryData } = useContentLibrary(libraryId);
Expand All @@ -30,25 +33,23 @@ export const NoComponents = ({ searchType = 'component' }: NoSearchResultsProps)

return (
<Stack direction="horizontal" gap={3} className="mt-6 justify-content-center">
{searchType === 'collection'
? <FormattedMessage {...messages.noCollections} />
: <FormattedMessage {...messages.noComponents} />}
<FormattedMessage {...infoText} />
{canEditLibrary && (
<Button iconBefore={Add} onClick={handleOnClickButton}>
{searchType === 'collection'
? <FormattedMessage {...messages.addCollection} />
: <FormattedMessage {...messages.addComponent} />}
<FormattedMessage {...addBtnText} />
</Button>
)}
</Stack>
);
};

export const NoSearchResults = ({ searchType = 'component' }: NoSearchResultsProps) => (
export const NoSearchResults = ({
infoText = messages.noSearchResults,
}: {
infoText: MessageDescriptor;
}) => (
<Stack direction="horizontal" gap={3} className="my-6 justify-content-center">
{searchType === 'collection'
? <FormattedMessage {...messages.noSearchResultsCollections} />
: <FormattedMessage {...messages.noSearchResults} />}
<FormattedMessage {...infoText} />
<ClearFiltersButton variant="primary" size="md" />
</Stack>
);
2 changes: 1 addition & 1 deletion src/library-authoring/LibraryAuthoringPage.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
}

.library-authoring-sidebar {
min-width: 300px;
min-width: 320px;
max-width: map-get($grid-breakpoints, "sm");
z-index: 1001; // to appear over header
position: sticky;
Expand Down
45 changes: 13 additions & 32 deletions src/library-authoring/collections/CollectionInfo.tsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,27 @@
import React from 'react';
import { useIntl } from '@edx/frontend-platform/i18n';
import {
Tab,
Tabs,
Stack,
} from '@openedx/paragon';

import messages from './messages';
import { useCollection } from '../data/apiHooks';
import Loading from '../../generic/Loading';

interface CollectionInfoProps {
collectionId: string;
libraryId: string;
}

const CollectionInfo = ({ libraryId, collectionId } : CollectionInfoProps) => {
const CollectionInfo = () => {
const intl = useIntl();
const { data: collectionData, isLoading: isCollectionLoading } = useCollection(libraryId, collectionId);

if (isCollectionLoading) {
return <Loading />;
}

return (
<Stack>
<div className="d-flex flex-wrap">
{collectionData?.title}
</div>
<Tabs
variant="tabs"
className="my-3 d-flex justify-content-around"
defaultActiveKey="manage"
>
<Tab eventKey="manage" title={intl.formatMessage(messages.manageTabTitle)}>
Manage tab placeholder
</Tab>
<Tab eventKey="details" title={intl.formatMessage(messages.detailsTabTitle)}>
Details tab placeholder
</Tab>
</Tabs>
</Stack>
<Tabs
variant="tabs"
className="my-3 d-flex justify-content-around"
defaultActiveKey="manage"
>
<Tab eventKey="manage" title={intl.formatMessage(messages.manageTabTitle)}>
Manage tab placeholder
</Tab>
<Tab eventKey="details" title={intl.formatMessage(messages.detailsTabTitle)}>
Details tab placeholder
</Tab>
</Tabs>
);
};

Expand Down
15 changes: 15 additions & 0 deletions src/library-authoring/collections/CollectionInfoHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Collection } from '../data/api';

interface CollectionInfoHeaderProps {
collection?: Collection;
}

const CollectionInfoHeader = ({ collection } : CollectionInfoHeaderProps) => {
return (
<div className="d-flex flex-wrap">
{collection?.title}
</div>
);
};

export default CollectionInfoHeader;
24 changes: 24 additions & 0 deletions src/library-authoring/collections/LibraryCollectionComponents.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Stack } from '@openedx/paragon';
import { NoComponents, NoSearchResults } from '../EmptyStates';
import { useSearchContext } from '../../search-manager';
import { LibraryComponents } from '../components';
import messages from './messages';

const LibraryCollectionComponents = ({ libraryId }: { libraryId: string }) => {
const { totalHits: componentCount, isFiltered } = useSearchContext();

if (componentCount === 0) {
return isFiltered ?
<NoSearchResults infoText={messages.noSearchResultsInCollection} />
: <NoComponents infoText={messages.noComponentsInCollection} addBtnText={messages.addComponentsInCollection} />;
}

return (
<Stack direction="vertical" gap={3}>
<h3 className="text-gray">Content ({componentCount})</h3>
<LibraryComponents libraryId={libraryId} variant="full" />
</Stack>
);
};

export default LibraryCollectionComponents;
40 changes: 27 additions & 13 deletions src/library-authoring/collections/LibraryCollectionPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useContext, useEffect } from 'react';
import { useContext, useEffect } from 'react';
import { StudioFooter } from '@edx/frontend-component-footer';
import { useIntl } from '@edx/frontend-platform/i18n';
import {
Expand All @@ -8,6 +8,7 @@ import {
Container,
Icon,
IconButton,
Row,
Stack,
} from '@openedx/paragon';
import { Add, InfoOutline } from '@openedx/paragon/icons';
Expand All @@ -29,12 +30,9 @@ import { useCollection, useContentLibrary } from '../data/apiHooks';
import { LibraryContext } from '../common/context';
import messages from '../messages';
import { LibrarySidebar } from '../library-sidebar';
import LibraryCollectionComponents from './LibraryCollectionComponents';

interface HeaderActionsProps {
canEditLibrary: boolean;
}

const HeaderActions = ({ canEditLibrary }: HeaderActionsProps) => {
const HeaderActions = ({ canEditLibrary }: { canEditLibrary: boolean; }) => {
const intl = useIntl();
const {
openAddContentSidebar,
Expand All @@ -59,7 +57,15 @@ const HeaderActions = ({ canEditLibrary }: HeaderActionsProps) => {
);
};

const SubHeaderTitle = ({ title, canEditLibrary, infoClickHandler }: { title: string, canEditLibrary: boolean, infoClickHandler: () => void }) => {
const SubHeaderTitle = ({
title,
canEditLibrary,
infoClickHandler,
}: {
title: string;
canEditLibrary: boolean;
infoClickHandler: () => void;
}) => {
const intl = useIntl();

return (
Expand All @@ -85,7 +91,13 @@ const SubHeaderTitle = ({ title, canEditLibrary, infoClickHandler }: { title: st
);
};

const LibraryCollectionPage = ({ libraryId, collectionId }: { libraryId: string, collectionId: string }) => {
const LibraryCollectionPage = ({
libraryId,
collectionId,
}: {
libraryId: string;
collectionId: string;
}) => {
const intl = useIntl();

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

useEffect(() => {
openCollectionInfoSidebar(collectionId);
openCollectionInfoSidebar();
}, []);

const { data: libraryData, isLoading: isLibLoading } = useContentLibrary(libraryId);
Expand Down Expand Up @@ -139,7 +151,7 @@ const LibraryCollectionPage = ({ libraryId, collectionId }: { libraryId: string,
title={<SubHeaderTitle
title={collectionData.title}
canEditLibrary={libraryData.canEditLibrary}
infoClickHandler={() => openCollectionInfoSidebar(collectionId)}
infoClickHandler={openCollectionInfoSidebar}
/>}
breadcrumbs={(
<Breadcrumb
Expand All @@ -151,19 +163,20 @@ const LibraryCollectionPage = ({ libraryId, collectionId }: { libraryId: string,
headerActions={<HeaderActions canEditLibrary={libraryData.canEditLibrary} />}
/>
<SearchKeywordsField className="w-50" />
<div className="d-flex mt-3 align-items-center">
<div className="d-flex mt-3 mb-4 align-items-center">
<FilterByTags />
<FilterByBlockType />
<ClearFiltersButton />
<div className="flex-grow-1" />
<SearchSortWidget />
</div>
<LibraryCollectionComponents libraryId={libraryId} />
</Container>
<StudioFooter />
</div>
{ !!sidebarBodyComponent && (
<div className="library-authoring-sidebar box-shadow-left-1 bg-white" data-testid="library-sidebar">
<LibrarySidebar library={libraryData} />
<LibrarySidebar library={libraryData} collection={collectionData} />
</div>
)}
</div>
Expand All @@ -178,7 +191,8 @@ const LibraryCollectionPageWrapper = () => {

return (
<SearchContextProvider
extraFilter={`context_key = "${libraryId}"`}
extraFilter={[`context_key = "${libraryId}"`, `collections.key = "${collectionId}"`]}
fetchCollections={false}
>
<LibraryCollectionPage libraryId={libraryId} collectionId={collectionId} />
</SearchContextProvider>
Expand Down
9 changes: 6 additions & 3 deletions src/library-authoring/collections/LibraryCollections.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useSearchContext } from '../../search-manager';
import { NoComponents, NoSearchResults } from '../EmptyStates';
import CollectionCard from '../components/CollectionCard';
import { LIBRARY_SECTION_PREVIEW_LIMIT } from '../components/LibrarySection';
import messages from '../messages';

type LibraryCollectionsProps = {
variant: 'full' | 'preview',
Expand Down Expand Up @@ -37,7 +38,9 @@ const LibraryCollections = ({ variant }: LibraryCollectionsProps) => {
);

if (totalCollectionHits === 0) {
return isFiltered ? <NoSearchResults searchType="collection" /> : <NoComponents searchType="collection" />;
return isFiltered ?
<NoSearchResults infoText={messages.noSearchResultsCollections} />
: <NoComponents infoText={messages.noCollections} addBtnText={messages.addCollection} />;
}

return (
Expand All @@ -50,12 +53,12 @@ const LibraryCollections = ({ variant }: LibraryCollectionsProps) => {
}}
hasEqualColumnHeights
>
{ collectionList.map((collectionHit) => (
{collectionList.map((collectionHit) => (
<CollectionCard
key={collectionHit.id}
collectionHit={collectionHit}
/>
)) }
))}
</CardGrid>
);
};
Expand Down
1 change: 1 addition & 0 deletions src/library-authoring/collections/index.tsx
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { default as CollectionInfo } from './CollectionInfo';
export { default as CollectionInfoHeader } from './CollectionInfoHeader';
15 changes: 15 additions & 0 deletions src/library-authoring/collections/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,21 @@ const messages = defineMessages({
defaultMessage: 'Details',
description: 'Title for details tab',
},
noComponentsInCollection: {
id: 'course-authoring.library-authoring.collections-pag.no-components.text',
defaultMessage: 'This collection is currently empty.',
description: 'Text to display when collection has no associated components',
},
addComponentsInCollection: {
id: 'course-authoring.library-authoring.collections-pag.add-components.btn-text',
defaultMessage: 'New',
description: 'Text to display in new button if no components in collection is found',
},
noSearchResultsInCollection: {
id: 'course-authoring.library-authoring.collections-pag.no-search-results.text',
defaultMessage: 'No matching components found in this collections.',
description: 'Message displayed when no matching components are found in collection',
},
});

export default messages;
15 changes: 6 additions & 9 deletions src/library-authoring/common/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export interface LibraryContextData {
isCreateCollectionModalOpen: boolean;
openCreateCollectionModal: () => void;
closeCreateCollectionModal: () => void;
openCollectionInfoSidebar: (collectionId: string) => void;
openCollectionInfoSidebar: () => void;
}

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

/**
Expand Down Expand Up @@ -61,13 +61,10 @@ export const LibraryProvider = (props: { children?: React.ReactNode }) => {
},
[],
);
const openCollectionInfoSidebar = React.useCallback(
(collectionId: string) => {
setCurrentComponentKey(collectionId);
setSidebarBodyComponent(SidebarBodyComponentId.CollectionInfo);
},
[],
);
const openCollectionInfoSidebar = React.useCallback(() => {
setCurrentComponentKey(undefined);
setSidebarBodyComponent(SidebarBodyComponentId.CollectionInfo);
}, []);

const context = React.useMemo(() => ({
sidebarBodyComponent,
Expand Down
Loading

0 comments on commit f4a974b

Please sign in to comment.