-
-
-
-
-
+export const MigrateLegacyLibrariesAlert = () => {
+ const navigate = useNavigate();
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
-
-
-);
+
+ );
+};
diff --git a/src/studio-home/tabs-section/libraries-tab/index.tsx b/src/studio-home/tabs-section/libraries-tab/index.tsx
index 5ac5c9d26b..952946bb90 100644
--- a/src/studio-home/tabs-section/libraries-tab/index.tsx
+++ b/src/studio-home/tabs-section/libraries-tab/index.tsx
@@ -1,3 +1,4 @@
+import { useCallback, useState } from 'react';
import { useIntl } from '@edx/frontend-platform/i18n';
import {
ActionRow, Form, Icon, Menu, MenuItem, Pagination, Row, SearchField,
@@ -9,11 +10,52 @@ import { LoadingSpinner } from '@src/generic/Loading';
import AlertMessage from '@src/generic/alert-message';
import { useLibrariesV1Data } from '@src/studio-home/data/apiHooks';
import CardItem from '@src/studio-home/card-item';
-import { useCallback, useState } from 'react';
import SearchFilterWidget from '@src/search-manager/SearchFilterWidget';
+import type { LibraryV1Data } from '@src/studio-home/data/api';
+
import messages from '../messages';
import { MigrateLegacyLibrariesAlert } from './MigrateLegacyLibrariesAlert';
+const CardList = ({
+ data,
+ inSelectMode,
+}: {
+ data: LibraryV1Data[],
+ inSelectMode: boolean,
+}) => (
+ // eslint-disable-next-line react/jsx-no-useless-fragment
+ <>
+ {
+ data?.map(({
+ displayName,
+ org,
+ number,
+ url,
+ isMigrated,
+ migratedToKey,
+ migratedToTitle,
+ migratedToCollectionKey,
+ libraryKey,
+ }) => (
+
+ ))
+ }
+ >
+);
+
function findInValues
(arr: T[] | undefined, searchValue: string) {
return arr?.filter(o => Object.values(o).some(value => String(value).toLowerCase().includes(
String(searchValue).toLowerCase().trim(),
@@ -96,7 +138,17 @@ const MigrationFilter = ({ filters, setFilters }: MigrationFilterProps) => {
);
};
-const LibrariesTab = () => {
+interface LibrariesListProps {
+ selectedIds?: string[];
+ handleCheck?: (library: LibraryV1Data, action: 'add' | 'remove') => void;
+ hideMigationAlert?: boolean;
+}
+
+const LibrariesList = ({
+ selectedIds,
+ handleCheck,
+ hideMigationAlert = false,
+}: LibrariesListProps) => {
const intl = useIntl();
const { isPending, data, isError } = useLibrariesV1Data();
const [currentPage, setCurrentPage] = useState(1);
@@ -111,6 +163,21 @@ const LibrariesTab = () => {
const perPage = 10;
const totalPages = Math.ceil(filteredData.length / perPage);
const currentPageData = filteredData.slice((currentPage - 1) * perPage, currentPage * perPage);
+ const inSelectMode = handleCheck !== undefined;
+
+ const handleChangeCheckboxSet = useCallback((event) => {
+ if (handleCheck) {
+ const libraryId = event.target.value;
+ const library = currentPageData.find((item) => item.libraryKey === libraryId);
+ if (library) {
+ if (event.target.checked) {
+ handleCheck(library, 'add');
+ } else {
+ handleCheck(library, 'remove');
+ }
+ }
+ }
+ }, [handleCheck, currentPageData]);
if (isPending) {
return (
@@ -136,7 +203,7 @@ const LibrariesTab = () => {
return (
<>
- {getConfig().ENABLE_LEGACY_LIBRARY_MIGRATOR === 'true' && ()}
+ {!hideMigationAlert && getConfig().ENABLE_LEGACY_LIBRARY_MIGRATOR === 'true' && ()}
{
>
)}
- {currentPageData?.map(({
- displayName, org, number, url, isMigrated, migratedToKey, migratedToTitle, migratedToCollectionKey,
- }) => (
-
+
+
+ ) : (
+
- ))}
+ )}
{
totalPages > 1
&& (
@@ -192,4 +260,4 @@ const LibrariesTab = () => {
);
};
-export default LibrariesTab;
+export default LibrariesList;
diff --git a/src/studio-home/tabs-section/libraries-v2-tab/WelcomeLibrariesV2Alert.tsx b/src/studio-home/tabs-section/libraries-v2-tab/WelcomeLibrariesV2Alert.tsx
index 1719a1377f..cc71d732ae 100644
--- a/src/studio-home/tabs-section/libraries-v2-tab/WelcomeLibrariesV2Alert.tsx
+++ b/src/studio-home/tabs-section/libraries-v2-tab/WelcomeLibrariesV2Alert.tsx
@@ -1,5 +1,6 @@
import { Alert, Button, Hyperlink } from '@openedx/paragon';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
+import { useNavigate } from 'react-router-dom';
import { useLibrariesV1Data } from '@src/studio-home/data/apiHooks';
@@ -17,6 +18,7 @@ const libraryDocsLink = (
export const WelcomeLibrariesV2Alert = () => {
const { data, isPending, isError } = useLibrariesV1Data();
+ const navigate = useNavigate();
// Does not show the alert if we are still loading or if there was an error fetching libraries
if (isPending || isError) {
@@ -37,7 +39,7 @@ export const WelcomeLibrariesV2Alert = () => {
-
diff --git a/src/studio-home/tabs-section/libraries-v2-tab/index.tsx b/src/studio-home/tabs-section/libraries-v2-tab/index.tsx
index de7a56e68a..dd348139be 100644
--- a/src/studio-home/tabs-section/libraries-v2-tab/index.tsx
+++ b/src/studio-home/tabs-section/libraries-v2-tab/index.tsx
@@ -1,32 +1,104 @@
-import React, { useState } from 'react';
+import React, { useCallback, useState } from 'react';
import {
Icon,
Row,
Pagination,
Alert,
Button,
+ Form,
+ Stack,
+ useToggle,
} from '@openedx/paragon';
-import { getConfig } from '@edx/frontend-platform';
-import { useIntl } from '@edx/frontend-platform/i18n';
-import { Error } from '@openedx/paragon/icons';
+import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
+import { Add, Error } from '@openedx/paragon/icons';
-import { useContentLibraryV2List } from '@src/library-authoring';
+import { CreateLibraryModal, useContentLibraryV2List } from '@src/library-authoring';
import { LoadingSpinner } from '@src/generic/Loading';
import AlertMessage from '@src/generic/alert-message';
+import type { ContentLibrary, LibrariesV2Response } from '@src/library-authoring/data/api';
+
import CardItem from '../../card-item';
import messages from '../messages';
import LibrariesV2Filters from './libraries-v2-filters';
-import { WelcomeLibrariesV2Alert } from './WelcomeLibrariesV2Alert';
-type Props = Record;
+interface CardListProps {
+ hasV2Libraries: boolean;
+ selectMode?: 'single' | 'multiple';
+ isFiltered: boolean;
+ isLoading: boolean;
+ data: LibrariesV2Response;
+ handleClearFilters: () => void;
+}
-const LibrariesV2Tab: React.FC = () => {
+const CardList: React.FC = ({
+ hasV2Libraries,
+ selectMode,
+ isFiltered,
+ isLoading,
+ data,
+ handleClearFilters,
+}) => {
+ if (hasV2Libraries) {
+ return (
+ <>
+ {
+ data!.results.map(({
+ id, org, slug, title,
+ }) => (
+
+ ))
+ }
+ >
+ );
+ }
+
+ // Empty alert
+ if (isFiltered && !isLoading) {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+ return null;
+};
+
+interface Props {
+ selectedLibraryId?: string;
+ handleSelect?: (library: ContentLibrary) => void;
+ showCreateLibrary?: boolean;
+}
+
+const LibrariesV2List: React.FC = ({
+ selectedLibraryId,
+ handleSelect,
+ showCreateLibrary = false,
+}) => {
const intl = useIntl();
const [currentPage, setCurrentPage] = useState(1);
const [filterParams, setFilterParams] = useState({});
+ const [isCreateLibraryOpen, openCreateLibrary, closeCreateLibrary] = useToggle(false);
const isFiltered = Object.keys(filterParams).length > 0;
+ const inSelectMode = handleSelect !== undefined;
const handlePageSelect = (page: number) => {
setCurrentPage(page);
@@ -43,6 +115,22 @@ const LibrariesV2Tab: React.FC = () => {
isError,
} = useContentLibraryV2List({ page: currentPage, ...filterParams });
+ const handlePostCreateLibrary = useCallback((library: ContentLibrary) => {
+ if (handleSelect) {
+ handleSelect(library);
+ closeCreateLibrary();
+ }
+ }, [handleSelect, closeCreateLibrary]);
+
+ const handleOnChangeRadioSet = useCallback((libraryId: string) => {
+ if (handleSelect && data) {
+ const library = data.results.find((item) => item.id === libraryId);
+ if (library) {
+ handleSelect(library);
+ }
+ }
+ }, [data, handleSelect]);
+
if (isPending && !isFiltered) {
return (
@@ -54,22 +142,30 @@ const LibrariesV2Tab: React.FC = () => {
const hasV2Libraries = !isPending && !isError && ((data!.results.length || 0) > 0);
return (
- <>
- {getConfig().ENABLE_LEGACY_LIBRARY_MIGRATOR === 'true' && ()}
-
- {isError ? (
-
-
- {intl.formatMessage(messages.librariesTabErrorMessage)}
-
- )}
- />
- ) : (
-
-
+ isError ? (
+
+
+ {intl.formatMessage(messages.librariesTabErrorMessage)}
+
+ )}
+ />
+ ) : (
+
+
+
+ {showCreateLibrary && (
+
+
+
+ )}
= () => {
setFilterParams={setFilterParams}
setCurrentPage={setCurrentPage}
/>
- {!isPending && !isError
- && (
-
- {intl.formatMessage(messages.coursesPaginationInfo, {
- length: data.results.length,
- total: data.count,
- })}
-
- )}
-
-
- {hasV2Libraries
- ? data!.results.map(({
- id, org, slug, title,
- }) => (
-
- )) : isFiltered && !isPending && (
-
-
- {intl.formatMessage(messages.librariesV2TabLibraryNotFoundAlertTitle)}
-
-
- {intl.formatMessage(messages.librariesV2TabLibraryNotFoundAlertMessage)}
-
-
- {intl.formatMessage(messages.coursesTabCourseNotFoundAlertCleanFiltersButton)}
-
-
- )}
-
- {
- hasV2Libraries && (data!.numPages || 0) > 1
- && (
-
- )
- }
+
+ {!isPending && !isError
+ && (
+
+ {intl.formatMessage(messages.coursesPaginationInfo, {
+ length: data!.results.length,
+ total: data!.count,
+ })}
+
+ )}
- )}
- >
+
+ {inSelectMode ? (
+ handleOnChangeRadioSet(e.target.value)}
+ >
+
+
+ ) : (
+
+ )}
+
+ {
+ hasV2Libraries && (data!.numPages || 0) > 1
+ && (
+
+ )
+ }
+
+
+ )
);
};
-export default LibrariesV2Tab;
+export default LibrariesV2List;
diff --git a/src/studio-home/tabs-section/messages.ts b/src/studio-home/tabs-section/messages.ts
index 17303cb831..0563e25566 100644
--- a/src/studio-home/tabs-section/messages.ts
+++ b/src/studio-home/tabs-section/messages.ts
@@ -93,6 +93,11 @@ const messages = defineMessages({
defaultMessage: 'Review Legacy Libraries',
description: 'Label for the button to review legacy libraries',
},
+ createLibraryButton: {
+ id: 'studio-home.legacy-libraries.migrate.create-button',
+ defaultMessage: 'Create New Library',
+ description: 'Label for the button to create a new library in the library list view.',
+ },
librariesV1TabMigrationFilterLabel: {
id: 'course-authoring.studio-home.libraries.tab.migration.filter.label',
description: 'Label text for migration filter in legacy libraries tab',
diff --git a/src/utils.test.js b/src/utils.test.tsx
similarity index 99%
rename from src/utils.test.js
rename to src/utils.test.tsx
index d4fa59d373..9b59013edf 100644
--- a/src/utils.test.js
+++ b/src/utils.test.tsx
@@ -120,6 +120,7 @@ describe('FilesAndUploads utils', () => {
const dateStr = '2023-10-01T12:00:00Z';
const date = convertToDateFromString(dateStr);
expect(date).toBeInstanceOf(Date);
+ // @ts-ignore
expect(date.toISOString()).toBe('2023-10-01T12:00:00.000Z');
});
diff --git a/src/utils.ts b/src/utils.tsx
similarity index 99%
rename from src/utils.ts
rename to src/utils.tsx
index 7e68e996c7..1432a2ce19 100644
--- a/src/utils.ts
+++ b/src/utils.tsx
@@ -347,3 +347,5 @@ export const skipIfUnwantedTarget = (
}
onClick(e);
};
+
+export const BoldText = (chunk: string[]) =>
{chunk};