refactor: split up library context (#1539)
Split the library context into smaller contexts: * LibraryContext * ComponentPickerContext * SidebarContext
This commit is contained in:
@@ -6,7 +6,7 @@ import {
|
||||
import { Add } from '@openedx/paragon/icons';
|
||||
import { ClearFiltersButton } from '../search-manager';
|
||||
import messages from './messages';
|
||||
import { useLibraryContext } from './common/context';
|
||||
import { useLibraryContext } from './common/context/LibraryContext';
|
||||
|
||||
export const NoComponents = ({
|
||||
infoText = messages.noComponents,
|
||||
|
||||
@@ -37,19 +37,25 @@ import {
|
||||
} from '../search-manager';
|
||||
import LibraryContent, { ContentType } from './LibraryContent';
|
||||
import { LibrarySidebar } from './library-sidebar';
|
||||
import { SidebarBodyComponentId, useLibraryContext } from './common/context';
|
||||
import { useComponentPickerContext } from './common/context/ComponentPickerContext';
|
||||
import { useLibraryContext } from './common/context/LibraryContext';
|
||||
import { SidebarBodyComponentId, useSidebarContext } from './common/context/SidebarContext';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
const HeaderActions = () => {
|
||||
const intl = useIntl();
|
||||
|
||||
const { readOnly } = useLibraryContext();
|
||||
|
||||
const {
|
||||
componentPickerMode,
|
||||
openAddContentSidebar,
|
||||
openInfoSidebar,
|
||||
closeLibrarySidebar,
|
||||
sidebarComponentInfo,
|
||||
readOnly,
|
||||
} = useLibraryContext();
|
||||
} = useSidebarContext();
|
||||
|
||||
const { componentPickerMode } = useComponentPickerContext();
|
||||
|
||||
const infoSidebarIsOpen = () => (
|
||||
sidebarComponentInfo?.type === SidebarBodyComponentId.Info
|
||||
@@ -94,7 +100,8 @@ const HeaderActions = () => {
|
||||
const SubHeaderTitle = ({ title }: { title: string }) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const { readOnly, componentPickerMode } = useLibraryContext();
|
||||
const { readOnly } = useLibraryContext();
|
||||
const { componentPickerMode } = useComponentPickerContext();
|
||||
|
||||
const showReadOnlyBadge = readOnly && !componentPickerMode;
|
||||
|
||||
@@ -127,16 +134,14 @@ const LibraryAuthoringPage = ({ returnToLibrarySelection }: LibraryAuthoringPage
|
||||
librariesV2Enabled,
|
||||
} = useStudioHome();
|
||||
|
||||
const { componentPickerMode, restrictToLibrary } = useComponentPickerContext();
|
||||
const {
|
||||
libraryId,
|
||||
libraryData,
|
||||
isLoadingLibraryData,
|
||||
componentPickerMode,
|
||||
restrictToLibrary,
|
||||
showOnlyPublished,
|
||||
sidebarComponentInfo,
|
||||
openInfoSidebar,
|
||||
} = useLibraryContext();
|
||||
const { openInfoSidebar, sidebarComponentInfo } = useSidebarContext();
|
||||
|
||||
const [activeKey, setActiveKey] = useState<ContentType | undefined>(ContentType.home);
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
import { getContentSearchConfigUrl } from '../search-manager/data/api';
|
||||
import { mockContentLibrary } from './data/api.mocks';
|
||||
import mockEmptyResult from '../search-modal/__mocks__/empty-search-result.json';
|
||||
import { LibraryProvider } from './common/context';
|
||||
import { LibraryProvider } from './common/context/LibraryContext';
|
||||
import LibraryContent from './LibraryContent';
|
||||
import { libraryComponentsMock } from './__mocks__';
|
||||
|
||||
@@ -56,7 +56,9 @@ const clipboardBroadcastChannelMock = {
|
||||
|
||||
const withLibraryId = (libraryId: string) => ({
|
||||
extraWrapper: ({ children }: { children: React.ReactNode }) => (
|
||||
<LibraryProvider libraryId={libraryId}>{children}</LibraryProvider>
|
||||
<LibraryProvider libraryId={libraryId}>
|
||||
{children}
|
||||
</LibraryProvider>
|
||||
),
|
||||
});
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@ import { useEffect } from 'react';
|
||||
import { LoadingSpinner } from '../generic/Loading';
|
||||
import { useSearchContext } from '../search-manager';
|
||||
import { NoComponents, NoSearchResults } from './EmptyStates';
|
||||
import { useLibraryContext } from './common/context';
|
||||
import { useLibraryContext } from './common/context/LibraryContext';
|
||||
import { useSidebarContext } from './common/context/SidebarContext';
|
||||
import CollectionCard from './components/CollectionCard';
|
||||
import ComponentCard from './components/ComponentCard';
|
||||
import { useLoadOnScroll } from '../hooks';
|
||||
@@ -37,7 +38,8 @@ const LibraryContent = ({ contentType = ContentType.home }: LibraryContentProps)
|
||||
isFiltered,
|
||||
usageKey,
|
||||
} = useSearchContext();
|
||||
const { openAddContentSidebar, openComponentInfoSidebar, openCreateCollectionModal } = useLibraryContext();
|
||||
const { openCreateCollectionModal } = useLibraryContext();
|
||||
const { openAddContentSidebar, openComponentInfoSidebar } = useSidebarContext();
|
||||
|
||||
useEffect(() => {
|
||||
if (usageKey) {
|
||||
|
||||
@@ -6,9 +6,9 @@ import {
|
||||
} from 'react-router-dom';
|
||||
|
||||
import LibraryAuthoringPage from './LibraryAuthoringPage';
|
||||
import { LibraryProvider } from './common/context';
|
||||
import { LibraryProvider } from './common/context/LibraryContext';
|
||||
import { SidebarProvider } from './common/context/SidebarContext';
|
||||
import { CreateCollectionModal } from './create-collection';
|
||||
import { LibraryTeamModal } from './library-team';
|
||||
import LibraryCollectionPage from './collections/LibraryCollectionPage';
|
||||
import { ComponentPicker } from './component-picker';
|
||||
import { ComponentEditorModal } from './components/ComponentEditorModal';
|
||||
@@ -27,6 +27,8 @@ const LibraryLayout = () => {
|
||||
|
||||
return (
|
||||
<LibraryProvider
|
||||
/** We need to pass the collectionId as key to the LibraryProvider to force a re-render
|
||||
* when we navigate to a collection page. */
|
||||
key={collectionId}
|
||||
libraryId={libraryId}
|
||||
collectionId={collectionId}
|
||||
@@ -36,19 +38,20 @@ const LibraryLayout = () => {
|
||||
* Sidebar > AddContentContainer > ComponentPicker */
|
||||
componentPicker={ComponentPicker}
|
||||
>
|
||||
<Routes>
|
||||
<Route
|
||||
path="collection/:collectionId"
|
||||
element={<LibraryCollectionPage />}
|
||||
/>
|
||||
<Route
|
||||
path="*"
|
||||
element={<LibraryAuthoringPage />}
|
||||
/>
|
||||
</Routes>
|
||||
<CreateCollectionModal />
|
||||
<ComponentEditorModal />
|
||||
<LibraryTeamModal />
|
||||
<SidebarProvider>
|
||||
<Routes>
|
||||
<Route
|
||||
path="collection/:collectionId"
|
||||
element={<LibraryCollectionPage />}
|
||||
/>
|
||||
<Route
|
||||
path="*"
|
||||
element={<LibraryAuthoringPage />}
|
||||
/>
|
||||
</Routes>
|
||||
<CreateCollectionModal />
|
||||
<ComponentEditorModal />
|
||||
</SidebarProvider>
|
||||
</LibraryProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
getContentLibraryApiUrl, getCreateLibraryBlockUrl, getLibraryCollectionComponentApiUrl, getLibraryPasteClipboardUrl,
|
||||
} from '../data/api';
|
||||
import { mockBroadcastChannel, mockClipboardEmpty, mockClipboardHtml } from '../../generic/data/api.mock';
|
||||
import { LibraryProvider } from '../common/context';
|
||||
import { LibraryProvider } from '../common/context/LibraryContext';
|
||||
import AddContentContainer from './AddContentContainer';
|
||||
import { ComponentEditorModal } from '../components/ComponentEditorModal';
|
||||
import editorCmsApi from '../../editors/data/services/cms/api';
|
||||
|
||||
@@ -25,7 +25,7 @@ import { ToastContext } from '../../generic/toast-context';
|
||||
import { useCopyToClipboard } from '../../generic/clipboard';
|
||||
import { getCanEdit } from '../../course-unit/data/selectors';
|
||||
import { useCreateLibraryBlock, useLibraryPasteClipboard, useAddComponentsToCollection } from '../data/apiHooks';
|
||||
import { useLibraryContext } from '../common/context';
|
||||
import { useLibraryContext } from '../common/context/LibraryContext';
|
||||
import { canEditComponent } from '../components/ComponentEditorModal';
|
||||
import { PickLibraryContentModal } from './PickLibraryContentModal';
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
import { studioHomeMock } from '../../studio-home/__mocks__';
|
||||
import { getStudioHomeApiUrl } from '../../studio-home/data/api';
|
||||
import mockResult from '../__mocks__/library-search.json';
|
||||
import { LibraryProvider } from '../common/context';
|
||||
import { LibraryProvider } from '../common/context/LibraryContext';
|
||||
import { ComponentPicker } from '../component-picker';
|
||||
import * as api from '../data/api';
|
||||
import {
|
||||
|
||||
@@ -3,7 +3,8 @@ import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { ActionRow, Button, StandardModal } from '@openedx/paragon';
|
||||
|
||||
import { ToastContext } from '../../generic/toast-context';
|
||||
import { type SelectedComponent, useLibraryContext } from '../common/context';
|
||||
import { useLibraryContext } from '../common/context/LibraryContext';
|
||||
import type { SelectedComponent } from '../common/context/ComponentPickerContext';
|
||||
import { useAddComponentsToCollection } from '../data/apiHooks';
|
||||
import messages from './messages';
|
||||
|
||||
|
||||
@@ -10,7 +10,8 @@ import {
|
||||
waitFor,
|
||||
within,
|
||||
} from '../../testUtils';
|
||||
import { LibraryProvider, SidebarBodyComponentId } from '../common/context';
|
||||
import { LibraryProvider } from '../common/context/LibraryContext';
|
||||
import { SidebarBodyComponentId, SidebarProvider } from '../common/context/SidebarContext';
|
||||
import * as api from '../data/api';
|
||||
import { mockContentLibrary, mockGetCollectionMetadata } from '../data/api.mocks';
|
||||
import CollectionDetails from './CollectionDetails';
|
||||
@@ -30,14 +31,15 @@ const library = mockContentLibrary.libraryData;
|
||||
|
||||
const render = () => baseRender(<CollectionDetails />, {
|
||||
extraWrapper: ({ children }) => (
|
||||
<LibraryProvider
|
||||
libraryId={library.id}
|
||||
initialSidebarComponentInfo={{
|
||||
id: collectionId,
|
||||
type: SidebarBodyComponentId.CollectionInfo,
|
||||
}}
|
||||
>
|
||||
{ children }
|
||||
<LibraryProvider libraryId={library.id}>
|
||||
<SidebarProvider
|
||||
initialSidebarComponentInfo={{
|
||||
id: collectionId,
|
||||
type: SidebarBodyComponentId.CollectionInfo,
|
||||
}}
|
||||
>
|
||||
{ children }
|
||||
</SidebarProvider>
|
||||
</LibraryProvider>
|
||||
),
|
||||
});
|
||||
|
||||
@@ -6,7 +6,8 @@ import classNames from 'classnames';
|
||||
import { getItemIcon } from '../../generic/block-type-utils';
|
||||
import { ToastContext } from '../../generic/toast-context';
|
||||
import { BlockTypeLabel, useGetBlockTypes } from '../../search-manager';
|
||||
import { useLibraryContext } from '../common/context';
|
||||
import { useLibraryContext } from '../common/context/LibraryContext';
|
||||
import { useSidebarContext } from '../common/context/SidebarContext';
|
||||
import { useCollection, useUpdateCollection } from '../data/apiHooks';
|
||||
import HistoryWidget from '../generic/history-widget';
|
||||
import messages from './messages';
|
||||
@@ -37,7 +38,8 @@ const BlockCount = ({
|
||||
};
|
||||
|
||||
const CollectionStatsWidget = () => {
|
||||
const { libraryId, sidebarComponentInfo } = useLibraryContext();
|
||||
const { libraryId } = useLibraryContext();
|
||||
const { sidebarComponentInfo } = useSidebarContext();
|
||||
const collectionId = sidebarComponentInfo?.id;
|
||||
|
||||
const { data: blockTypes } = useGetBlockTypes([
|
||||
@@ -97,11 +99,8 @@ const CollectionStatsWidget = () => {
|
||||
const CollectionDetails = () => {
|
||||
const intl = useIntl();
|
||||
const { showToast } = useContext(ToastContext);
|
||||
const {
|
||||
libraryId,
|
||||
sidebarComponentInfo,
|
||||
readOnly,
|
||||
} = useLibraryContext();
|
||||
const { libraryId, readOnly } = useLibraryContext();
|
||||
const { sidebarComponentInfo } = useSidebarContext();
|
||||
|
||||
const collectionId = sidebarComponentInfo?.id;
|
||||
// istanbul ignore next: This should never happen
|
||||
|
||||
@@ -8,30 +8,27 @@ import {
|
||||
import { useCallback } from 'react';
|
||||
import { useNavigate, useMatch } from 'react-router-dom';
|
||||
|
||||
import { useComponentPickerContext } from '../common/context/ComponentPickerContext';
|
||||
import { useLibraryContext } from '../common/context/LibraryContext';
|
||||
import {
|
||||
useLibraryContext,
|
||||
type CollectionInfoTab,
|
||||
COLLECTION_INFO_TABS,
|
||||
isCollectionInfoTab,
|
||||
COMPONENT_INFO_TABS,
|
||||
} from '../common/context';
|
||||
import CollectionDetails from './CollectionDetails';
|
||||
import messages from './messages';
|
||||
isCollectionInfoTab,
|
||||
useSidebarContext,
|
||||
} from '../common/context/SidebarContext';
|
||||
import { ContentTagsDrawer } from '../../content-tags-drawer';
|
||||
import { buildCollectionUsageKey } from '../../generic/key-utils';
|
||||
import CollectionDetails from './CollectionDetails';
|
||||
import messages from './messages';
|
||||
|
||||
const CollectionInfo = () => {
|
||||
const intl = useIntl();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const {
|
||||
libraryId,
|
||||
collectionId,
|
||||
setCollectionId,
|
||||
sidebarComponentInfo,
|
||||
componentPickerMode,
|
||||
setSidebarCurrentTab,
|
||||
} = useLibraryContext();
|
||||
const { componentPickerMode } = useComponentPickerContext();
|
||||
const { libraryId, collectionId, setCollectionId } = useLibraryContext();
|
||||
const { sidebarComponentInfo, setSidebarCurrentTab } = useSidebarContext();
|
||||
|
||||
const tab: CollectionInfoTab = (
|
||||
sidebarComponentInfo?.currentTab && isCollectionInfoTab(sidebarComponentInfo.currentTab)
|
||||
@@ -43,7 +40,7 @@ const CollectionInfo = () => {
|
||||
throw new Error('sidebarCollectionId is required');
|
||||
}
|
||||
|
||||
const url = `/library/${libraryId}/collection/${sidebarCollectionId}/`;
|
||||
const url = `/library/${libraryId}/collection/${sidebarCollectionId}`;
|
||||
const urlMatch = useMatch(url);
|
||||
|
||||
const showOpenCollectionButton = !urlMatch && collectionId !== sidebarCollectionId;
|
||||
|
||||
@@ -8,7 +8,8 @@ import {
|
||||
screen,
|
||||
waitFor,
|
||||
} from '../../testUtils';
|
||||
import { LibraryProvider, SidebarBodyComponentId } from '../common/context';
|
||||
import { LibraryProvider } from '../common/context/LibraryContext';
|
||||
import { SidebarBodyComponentId, SidebarProvider } from '../common/context/SidebarContext';
|
||||
import { mockContentLibrary, mockGetCollectionMetadata } from '../data/api.mocks';
|
||||
import * as api from '../data/api';
|
||||
import CollectionInfoHeader from './CollectionInfoHeader';
|
||||
@@ -28,14 +29,15 @@ const { collectionId } = mockGetCollectionMetadata;
|
||||
|
||||
const render = (libraryId: string = mockLibraryId) => baseRender(<CollectionInfoHeader />, {
|
||||
extraWrapper: ({ children }) => (
|
||||
<LibraryProvider
|
||||
libraryId={libraryId}
|
||||
initialSidebarComponentInfo={{
|
||||
id: collectionId,
|
||||
type: SidebarBodyComponentId.CollectionInfo,
|
||||
}}
|
||||
>
|
||||
{ children }
|
||||
<LibraryProvider libraryId={libraryId}>
|
||||
<SidebarProvider
|
||||
initialSidebarComponentInfo={{
|
||||
id: collectionId,
|
||||
type: SidebarBodyComponentId.CollectionInfo,
|
||||
}}
|
||||
>
|
||||
{ children }
|
||||
</SidebarProvider>
|
||||
</LibraryProvider>
|
||||
),
|
||||
});
|
||||
|
||||
@@ -9,7 +9,8 @@ import {
|
||||
import { Edit } from '@openedx/paragon/icons';
|
||||
|
||||
import { ToastContext } from '../../generic/toast-context';
|
||||
import { useLibraryContext } from '../common/context';
|
||||
import { useLibraryContext } from '../common/context/LibraryContext';
|
||||
import { useSidebarContext } from '../common/context/SidebarContext';
|
||||
import { useCollection, useUpdateCollection } from '../data/apiHooks';
|
||||
import messages from './messages';
|
||||
|
||||
@@ -17,11 +18,8 @@ const CollectionInfoHeader = () => {
|
||||
const intl = useIntl();
|
||||
const [inputIsActive, setIsActive] = useState(false);
|
||||
|
||||
const {
|
||||
libraryId,
|
||||
sidebarComponentInfo,
|
||||
readOnly,
|
||||
} = useLibraryContext();
|
||||
const { libraryId, readOnly } = useLibraryContext();
|
||||
const { sidebarComponentInfo } = useSidebarContext();
|
||||
|
||||
const collectionId = sidebarComponentInfo?.id;
|
||||
// istanbul ignore if: this should never happen
|
||||
|
||||
@@ -2,12 +2,12 @@ import { Stack } from '@openedx/paragon';
|
||||
import { NoComponents, NoSearchResults } from '../EmptyStates';
|
||||
import { useSearchContext } from '../../search-manager';
|
||||
import messages from './messages';
|
||||
import { useLibraryContext } from '../common/context';
|
||||
import { useSidebarContext } from '../common/context/SidebarContext';
|
||||
import LibraryContent, { ContentType } from '../LibraryContent';
|
||||
|
||||
const LibraryCollectionComponents = () => {
|
||||
const { totalHits: componentCount, isFiltered } = useSearchContext();
|
||||
const { openAddContentSidebar } = useLibraryContext();
|
||||
const { openAddContentSidebar } = useSidebarContext();
|
||||
|
||||
if (componentCount === 0) {
|
||||
return isFiltered
|
||||
|
||||
@@ -27,17 +27,17 @@ import {
|
||||
SearchSortWidget,
|
||||
} from '../../search-manager';
|
||||
import { useCollection, useContentLibrary } from '../data/apiHooks';
|
||||
import { useLibraryContext } from '../common/context';
|
||||
import { useComponentPickerContext } from '../common/context/ComponentPickerContext';
|
||||
import { useLibraryContext } from '../common/context/LibraryContext';
|
||||
import { useSidebarContext } from '../common/context/SidebarContext';
|
||||
import messages from './messages';
|
||||
import { LibrarySidebar } from '../library-sidebar';
|
||||
import LibraryCollectionComponents from './LibraryCollectionComponents';
|
||||
|
||||
const HeaderActions = () => {
|
||||
const intl = useIntl();
|
||||
const {
|
||||
openAddContentSidebar,
|
||||
readOnly,
|
||||
} = useLibraryContext();
|
||||
const { readOnly } = useLibraryContext();
|
||||
const { openAddContentSidebar } = useSidebarContext();
|
||||
|
||||
if (readOnly) {
|
||||
return null;
|
||||
@@ -66,7 +66,8 @@ const SubHeaderTitle = ({
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const { readOnly, componentPickerMode } = useLibraryContext();
|
||||
const { componentPickerMode } = useComponentPickerContext();
|
||||
const { readOnly } = useLibraryContext();
|
||||
|
||||
const showReadOnlyBadge = readOnly && !componentPickerMode;
|
||||
|
||||
@@ -103,13 +104,9 @@ const LibraryCollectionPage = () => {
|
||||
throw new Error('Rendered without collectionId or libraryId URL parameter');
|
||||
}
|
||||
|
||||
const {
|
||||
sidebarComponentInfo,
|
||||
openCollectionInfoSidebar,
|
||||
componentPickerMode,
|
||||
showOnlyPublished,
|
||||
setCollectionId,
|
||||
} = useLibraryContext();
|
||||
const { componentPickerMode } = useComponentPickerContext();
|
||||
const { showOnlyPublished, setCollectionId } = useLibraryContext();
|
||||
const { sidebarComponentInfo, openCollectionInfoSidebar } = useSidebarContext();
|
||||
|
||||
const {
|
||||
data: collectionData,
|
||||
|
||||
@@ -1,394 +0,0 @@
|
||||
import { useToggle } from '@openedx/paragon';
|
||||
import React, {
|
||||
useCallback,
|
||||
useContext,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import type { ComponentPicker } from '../component-picker';
|
||||
import type { ContentLibrary } from '../data/api';
|
||||
import { useContentLibrary } from '../data/apiHooks';
|
||||
|
||||
export interface SelectedComponent {
|
||||
usageKey: string;
|
||||
blockType: string;
|
||||
}
|
||||
|
||||
export type ComponentSelectedEvent = (selectedComponent: SelectedComponent) => void;
|
||||
export type ComponentSelectionChangedEvent = (selectedComponents: SelectedComponent[]) => void;
|
||||
|
||||
type NoComponentPickerType = {
|
||||
componentPickerMode?: undefined;
|
||||
onComponentSelected?: never;
|
||||
selectedComponents?: never;
|
||||
addComponentToSelectedComponents?: never;
|
||||
removeComponentFromSelectedComponents?: never;
|
||||
restrictToLibrary?: never;
|
||||
/** The component picker modal to use. We need to pass it as a reference instead of
|
||||
* directly importing it to avoid the import cycle:
|
||||
* ComponentPicker > LibraryAuthoringPage/LibraryCollectionPage >
|
||||
* Sidebar > AddContentContainer > ComponentPicker */
|
||||
componentPicker?: typeof ComponentPicker;
|
||||
};
|
||||
|
||||
type ComponentPickerSingleType = {
|
||||
componentPickerMode: 'single';
|
||||
onComponentSelected: ComponentSelectedEvent;
|
||||
selectedComponents?: never;
|
||||
addComponentToSelectedComponents?: never;
|
||||
removeComponentFromSelectedComponents?: never;
|
||||
restrictToLibrary: boolean;
|
||||
componentPicker?: never;
|
||||
};
|
||||
|
||||
type ComponentPickerMultipleType = {
|
||||
componentPickerMode: 'multiple';
|
||||
onComponentSelected?: never;
|
||||
selectedComponents: SelectedComponent[];
|
||||
addComponentToSelectedComponents: ComponentSelectedEvent;
|
||||
removeComponentFromSelectedComponents: ComponentSelectedEvent;
|
||||
restrictToLibrary: boolean;
|
||||
componentPicker?: never;
|
||||
};
|
||||
|
||||
type ComponentPickerType = NoComponentPickerType | ComponentPickerSingleType | ComponentPickerMultipleType;
|
||||
|
||||
export enum SidebarBodyComponentId {
|
||||
AddContent = 'add-content',
|
||||
Info = 'info',
|
||||
ComponentInfo = 'component-info',
|
||||
CollectionInfo = 'collection-info',
|
||||
}
|
||||
|
||||
export const COLLECTION_INFO_TABS = {
|
||||
Manage: 'manage',
|
||||
Details: 'details',
|
||||
} as const;
|
||||
export type CollectionInfoTab = typeof COLLECTION_INFO_TABS[keyof typeof COLLECTION_INFO_TABS];
|
||||
export const isCollectionInfoTab = (tab: string): tab is CollectionInfoTab => (
|
||||
Object.values<string>(COLLECTION_INFO_TABS).includes(tab)
|
||||
);
|
||||
|
||||
export const COMPONENT_INFO_TABS = {
|
||||
Preview: 'preview',
|
||||
Manage: 'manage',
|
||||
Details: 'details',
|
||||
} as const;
|
||||
export type ComponentInfoTab = typeof COMPONENT_INFO_TABS[keyof typeof COMPONENT_INFO_TABS];
|
||||
export const isComponentInfoTab = (tab: string): tab is ComponentInfoTab => (
|
||||
Object.values<string>(COMPONENT_INFO_TABS).includes(tab)
|
||||
);
|
||||
|
||||
export interface SidebarComponentInfo {
|
||||
type: SidebarBodyComponentId;
|
||||
id: string;
|
||||
/** Additional action on Sidebar display */
|
||||
additionalAction?: SidebarAdditionalActions;
|
||||
/** Current tab in the sidebar */
|
||||
currentTab?: CollectionInfoTab | ComponentInfoTab;
|
||||
}
|
||||
|
||||
export interface ComponentEditorInfo {
|
||||
usageKey: string;
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
export enum SidebarAdditionalActions {
|
||||
JumpToAddCollections = 'jump-to-add-collections',
|
||||
}
|
||||
|
||||
export type LibraryContextData = {
|
||||
/** The ID of the current library */
|
||||
libraryId: string;
|
||||
libraryData?: ContentLibrary;
|
||||
readOnly: boolean;
|
||||
isLoadingLibraryData: boolean;
|
||||
collectionId: string | undefined;
|
||||
setCollectionId: (collectionId?: string) => void;
|
||||
// Only show published components
|
||||
showOnlyPublished: boolean;
|
||||
// Sidebar stuff - only one sidebar is active at any given time:
|
||||
closeLibrarySidebar: () => void;
|
||||
openAddContentSidebar: () => void;
|
||||
openInfoSidebar: () => void;
|
||||
openComponentInfoSidebar: (usageKey: string, additionalAction?: SidebarAdditionalActions) => void;
|
||||
sidebarComponentInfo?: SidebarComponentInfo;
|
||||
// "Library Team" modal
|
||||
isLibraryTeamModalOpen: boolean;
|
||||
openLibraryTeamModal: () => void;
|
||||
closeLibraryTeamModal: () => void;
|
||||
// "Create New Collection" modal
|
||||
isCreateCollectionModalOpen: boolean;
|
||||
openCreateCollectionModal: () => void;
|
||||
closeCreateCollectionModal: () => void;
|
||||
// Current collection
|
||||
openCollectionInfoSidebar: (collectionId: string, additionalAction?: SidebarAdditionalActions) => void;
|
||||
// Editor modal - for editing some component
|
||||
/** If the editor is open and the user is editing some component, this is the component being edited. */
|
||||
componentBeingEdited: ComponentEditorInfo | undefined;
|
||||
/** If an onClose callback is provided, it will be called when the editor is closed. */
|
||||
openComponentEditor: (usageKey: string, onClose?: () => void) => void;
|
||||
closeComponentEditor: () => void;
|
||||
resetSidebarAdditionalActions: () => void;
|
||||
setSidebarCurrentTab: (tab: CollectionInfoTab | ComponentInfoTab) => void;
|
||||
} & ComponentPickerType;
|
||||
|
||||
/**
|
||||
* Library Context.
|
||||
* Always available when we're in the context of a single library.
|
||||
*
|
||||
* Get this using `useLibraryContext()`
|
||||
*
|
||||
* Not used on the "library list" on Studio home.
|
||||
*/
|
||||
const LibraryContext = React.createContext<LibraryContextData | undefined>(undefined);
|
||||
|
||||
type NoComponentPickerProps = {
|
||||
componentPickerMode?: undefined;
|
||||
onComponentSelected?: never;
|
||||
onChangeComponentSelection?: never;
|
||||
restrictToLibrary?: never;
|
||||
componentPicker?: typeof ComponentPicker;
|
||||
};
|
||||
|
||||
export type ComponentPickerSingleProps = {
|
||||
componentPickerMode: 'single';
|
||||
onComponentSelected: ComponentSelectedEvent;
|
||||
onChangeComponentSelection?: never;
|
||||
restrictToLibrary?: boolean;
|
||||
componentPicker?: never;
|
||||
};
|
||||
|
||||
export type ComponentPickerMultipleProps = {
|
||||
componentPickerMode: 'multiple';
|
||||
onComponentSelected?: never;
|
||||
onChangeComponentSelection?: ComponentSelectionChangedEvent;
|
||||
restrictToLibrary?: boolean;
|
||||
componentPicker?: never;
|
||||
};
|
||||
|
||||
type ComponentPickerProps = NoComponentPickerProps | ComponentPickerSingleProps | ComponentPickerMultipleProps;
|
||||
|
||||
type LibraryProviderProps = {
|
||||
children?: React.ReactNode;
|
||||
libraryId: string;
|
||||
/** The initial collection ID to show */
|
||||
collectionId?: string;
|
||||
showOnlyPublished?: boolean;
|
||||
/** Only used for testing */
|
||||
initialSidebarComponentInfo?: SidebarComponentInfo;
|
||||
componentPicker?: typeof ComponentPicker;
|
||||
} & ComponentPickerProps;
|
||||
|
||||
/**
|
||||
* React component to provide `LibraryContext`
|
||||
*/
|
||||
export const LibraryProvider = ({
|
||||
children,
|
||||
libraryId,
|
||||
collectionId: collectionIdProp,
|
||||
componentPickerMode,
|
||||
restrictToLibrary = false,
|
||||
onComponentSelected,
|
||||
onChangeComponentSelection,
|
||||
showOnlyPublished = false,
|
||||
initialSidebarComponentInfo,
|
||||
componentPicker,
|
||||
}: LibraryProviderProps) => {
|
||||
const [collectionId, setCollectionId] = useState(collectionIdProp);
|
||||
const [sidebarComponentInfo, setSidebarComponentInfo] = useState<SidebarComponentInfo | undefined>(
|
||||
initialSidebarComponentInfo,
|
||||
);
|
||||
const [isLibraryTeamModalOpen, openLibraryTeamModal, closeLibraryTeamModal] = useToggle(false);
|
||||
const [isCreateCollectionModalOpen, openCreateCollectionModal, closeCreateCollectionModal] = useToggle(false);
|
||||
const [componentBeingEdited, setComponentBeingEdited] = useState<ComponentEditorInfo | undefined>();
|
||||
const closeComponentEditor = useCallback(() => {
|
||||
setComponentBeingEdited((prev) => {
|
||||
prev?.onClose?.();
|
||||
return undefined;
|
||||
});
|
||||
}, []);
|
||||
const openComponentEditor = useCallback((usageKey: string, onClose?: () => void) => {
|
||||
setComponentBeingEdited({ usageKey, onClose });
|
||||
}, []);
|
||||
|
||||
const [selectedComponents, setSelectedComponents] = useState<SelectedComponent[]>([]);
|
||||
|
||||
/** Helper function to consume addtional action once performed.
|
||||
Required to redo the action.
|
||||
*/
|
||||
const resetSidebarAdditionalActions = useCallback(() => {
|
||||
setSidebarComponentInfo((prev) => (prev && { ...prev, additionalAction: undefined }));
|
||||
}, []);
|
||||
|
||||
const closeLibrarySidebar = useCallback(() => {
|
||||
setSidebarComponentInfo(undefined);
|
||||
}, []);
|
||||
const openAddContentSidebar = useCallback(() => {
|
||||
setSidebarComponentInfo({ id: '', type: SidebarBodyComponentId.AddContent });
|
||||
}, []);
|
||||
const openInfoSidebar = useCallback(() => {
|
||||
setSidebarComponentInfo({ id: '', type: SidebarBodyComponentId.Info });
|
||||
}, []);
|
||||
|
||||
const openComponentInfoSidebar = useCallback((usageKey: string, additionalAction?: SidebarAdditionalActions) => {
|
||||
setSidebarComponentInfo((prev) => ({
|
||||
...prev,
|
||||
id: usageKey,
|
||||
type: SidebarBodyComponentId.ComponentInfo,
|
||||
additionalAction,
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const openCollectionInfoSidebar = useCallback((
|
||||
newCollectionId: string,
|
||||
additionalAction?: SidebarAdditionalActions,
|
||||
) => {
|
||||
setSidebarComponentInfo((prev) => ({
|
||||
...prev,
|
||||
id: newCollectionId,
|
||||
type: SidebarBodyComponentId.CollectionInfo,
|
||||
additionalAction,
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const addComponentToSelectedComponents = useCallback<ComponentSelectedEvent>((
|
||||
selectedComponent: SelectedComponent,
|
||||
) => {
|
||||
setSelectedComponents((prevSelectedComponents) => {
|
||||
// istanbul ignore if: this should never happen
|
||||
if (prevSelectedComponents.some((component) => component.usageKey === selectedComponent.usageKey)) {
|
||||
return prevSelectedComponents;
|
||||
}
|
||||
const newSelectedComponents = [...prevSelectedComponents, selectedComponent];
|
||||
onChangeComponentSelection?.(newSelectedComponents);
|
||||
return newSelectedComponents;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const removeComponentFromSelectedComponents = useCallback<ComponentSelectedEvent>((
|
||||
selectedComponent: SelectedComponent,
|
||||
) => {
|
||||
setSelectedComponents((prevSelectedComponents) => {
|
||||
// istanbul ignore if: this should never happen
|
||||
if (!prevSelectedComponents.some((component) => component.usageKey === selectedComponent.usageKey)) {
|
||||
return prevSelectedComponents;
|
||||
}
|
||||
const newSelectedComponents = prevSelectedComponents.filter(
|
||||
(component) => component.usageKey !== selectedComponent.usageKey,
|
||||
);
|
||||
onChangeComponentSelection?.(newSelectedComponents);
|
||||
return newSelectedComponents;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const setSidebarCurrentTab = useCallback((tab: CollectionInfoTab | ComponentInfoTab) => {
|
||||
setSidebarComponentInfo((prev) => (prev && { ...prev, currentTab: tab }));
|
||||
}, []);
|
||||
|
||||
const { data: libraryData, isLoading: isLoadingLibraryData } = useContentLibrary(libraryId);
|
||||
|
||||
const readOnly = !!componentPickerMode || !libraryData?.canEditLibrary;
|
||||
|
||||
const context = useMemo<LibraryContextData>(() => {
|
||||
const contextValue = {
|
||||
libraryId,
|
||||
libraryData,
|
||||
collectionId,
|
||||
setCollectionId,
|
||||
readOnly,
|
||||
isLoadingLibraryData,
|
||||
showOnlyPublished,
|
||||
closeLibrarySidebar,
|
||||
openAddContentSidebar,
|
||||
openInfoSidebar,
|
||||
openComponentInfoSidebar,
|
||||
sidebarComponentInfo,
|
||||
isLibraryTeamModalOpen,
|
||||
openLibraryTeamModal,
|
||||
closeLibraryTeamModal,
|
||||
isCreateCollectionModalOpen,
|
||||
openCreateCollectionModal,
|
||||
closeCreateCollectionModal,
|
||||
openCollectionInfoSidebar,
|
||||
componentBeingEdited,
|
||||
openComponentEditor,
|
||||
closeComponentEditor,
|
||||
resetSidebarAdditionalActions,
|
||||
setSidebarCurrentTab,
|
||||
};
|
||||
if (!componentPickerMode) {
|
||||
return {
|
||||
...contextValue,
|
||||
componentPicker,
|
||||
};
|
||||
}
|
||||
if (componentPickerMode === 'single') {
|
||||
return {
|
||||
...contextValue,
|
||||
componentPickerMode,
|
||||
restrictToLibrary,
|
||||
onComponentSelected,
|
||||
};
|
||||
}
|
||||
if (componentPickerMode === 'multiple') {
|
||||
return {
|
||||
...contextValue,
|
||||
componentPickerMode,
|
||||
restrictToLibrary,
|
||||
selectedComponents,
|
||||
addComponentToSelectedComponents,
|
||||
removeComponentFromSelectedComponents,
|
||||
};
|
||||
}
|
||||
return contextValue;
|
||||
}, [
|
||||
libraryId,
|
||||
collectionId,
|
||||
setCollectionId,
|
||||
libraryData,
|
||||
readOnly,
|
||||
isLoadingLibraryData,
|
||||
showOnlyPublished,
|
||||
componentPickerMode,
|
||||
restrictToLibrary,
|
||||
onComponentSelected,
|
||||
addComponentToSelectedComponents,
|
||||
removeComponentFromSelectedComponents,
|
||||
selectedComponents,
|
||||
onChangeComponentSelection,
|
||||
closeLibrarySidebar,
|
||||
openAddContentSidebar,
|
||||
openInfoSidebar,
|
||||
openComponentInfoSidebar,
|
||||
sidebarComponentInfo,
|
||||
isLibraryTeamModalOpen,
|
||||
openLibraryTeamModal,
|
||||
closeLibraryTeamModal,
|
||||
isCreateCollectionModalOpen,
|
||||
openCreateCollectionModal,
|
||||
closeCreateCollectionModal,
|
||||
openCollectionInfoSidebar,
|
||||
componentBeingEdited,
|
||||
openComponentEditor,
|
||||
closeComponentEditor,
|
||||
resetSidebarAdditionalActions,
|
||||
componentPicker,
|
||||
]);
|
||||
|
||||
return (
|
||||
<LibraryContext.Provider value={context}>
|
||||
{children}
|
||||
</LibraryContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export function useLibraryContext(): LibraryContextData {
|
||||
const ctx = useContext(LibraryContext);
|
||||
if (ctx === undefined) {
|
||||
/* istanbul ignore next */
|
||||
throw new Error('useLibraryContext() was used in a component without a <LibraryProvider> ancestor.');
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
164
src/library-authoring/common/context/ComponentPickerContext.tsx
Normal file
164
src/library-authoring/common/context/ComponentPickerContext.tsx
Normal file
@@ -0,0 +1,164 @@
|
||||
import {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
export interface SelectedComponent {
|
||||
usageKey: string;
|
||||
blockType: string;
|
||||
}
|
||||
|
||||
export type ComponentSelectedEvent = (selectedComponent: SelectedComponent) => void;
|
||||
export type ComponentSelectionChangedEvent = (selectedComponents: SelectedComponent[]) => void;
|
||||
|
||||
type NoComponentPickerType = {
|
||||
componentPickerMode: false;
|
||||
/** We add the `never` type to ensure that the other properties are not used,
|
||||
* but allow it to be desconstructed from the return value of `useComponentPickerContext()`
|
||||
*/
|
||||
onComponentSelected?: never;
|
||||
selectedComponents?: never;
|
||||
addComponentToSelectedComponents?: never;
|
||||
removeComponentFromSelectedComponents?: never;
|
||||
restrictToLibrary?: never;
|
||||
};
|
||||
|
||||
type ComponentPickerSingleType = {
|
||||
componentPickerMode: 'single';
|
||||
onComponentSelected: ComponentSelectedEvent;
|
||||
selectedComponents?: never;
|
||||
addComponentToSelectedComponents?: never;
|
||||
removeComponentFromSelectedComponents?: never;
|
||||
restrictToLibrary: boolean;
|
||||
};
|
||||
|
||||
type ComponentPickerMultipleType = {
|
||||
componentPickerMode: 'multiple';
|
||||
onComponentSelected?: never;
|
||||
selectedComponents: SelectedComponent[];
|
||||
addComponentToSelectedComponents: ComponentSelectedEvent;
|
||||
removeComponentFromSelectedComponents: ComponentSelectedEvent;
|
||||
restrictToLibrary: boolean;
|
||||
};
|
||||
|
||||
type ComponentPickerContextData = ComponentPickerSingleType | ComponentPickerMultipleType;
|
||||
|
||||
/**
|
||||
* Component Picker Context.
|
||||
* This context is used to provide the component picker mode and the selected components.
|
||||
*
|
||||
* Get this using `useComponentPickerContext()`
|
||||
*/
|
||||
const ComponentPickerContext = createContext<ComponentPickerContextData | undefined>(undefined);
|
||||
|
||||
export type ComponentPickerSingleProps = {
|
||||
componentPickerMode: 'single';
|
||||
onComponentSelected: ComponentSelectedEvent;
|
||||
onChangeComponentSelection?: never;
|
||||
restrictToLibrary?: boolean;
|
||||
};
|
||||
|
||||
export type ComponentPickerMultipleProps = {
|
||||
componentPickerMode: 'multiple';
|
||||
onComponentSelected?: never;
|
||||
onChangeComponentSelection?: ComponentSelectionChangedEvent;
|
||||
restrictToLibrary?: boolean;
|
||||
};
|
||||
|
||||
type ComponentPickerProps = ComponentPickerSingleProps | ComponentPickerMultipleProps;
|
||||
|
||||
type ComponentPickerProviderProps = {
|
||||
children?: React.ReactNode;
|
||||
} & ComponentPickerProps;
|
||||
|
||||
/**
|
||||
* React component to provide `ComponentPickerContext`
|
||||
*/
|
||||
export const ComponentPickerProvider = ({
|
||||
children,
|
||||
componentPickerMode,
|
||||
restrictToLibrary = false,
|
||||
onComponentSelected,
|
||||
onChangeComponentSelection,
|
||||
}: ComponentPickerProviderProps) => {
|
||||
const [selectedComponents, setSelectedComponents] = useState<SelectedComponent[]>([]);
|
||||
|
||||
const addComponentToSelectedComponents = useCallback<ComponentSelectedEvent>((
|
||||
selectedComponent: SelectedComponent,
|
||||
) => {
|
||||
setSelectedComponents((prevSelectedComponents) => {
|
||||
// istanbul ignore if: this should never happen
|
||||
if (prevSelectedComponents.some((component) => component.usageKey === selectedComponent.usageKey)) {
|
||||
return prevSelectedComponents;
|
||||
}
|
||||
const newSelectedComponents = [...prevSelectedComponents, selectedComponent];
|
||||
onChangeComponentSelection?.(newSelectedComponents);
|
||||
return newSelectedComponents;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const removeComponentFromSelectedComponents = useCallback<ComponentSelectedEvent>((
|
||||
selectedComponent: SelectedComponent,
|
||||
) => {
|
||||
setSelectedComponents((prevSelectedComponents) => {
|
||||
// istanbul ignore if: this should never happen
|
||||
if (!prevSelectedComponents.some((component) => component.usageKey === selectedComponent.usageKey)) {
|
||||
return prevSelectedComponents;
|
||||
}
|
||||
const newSelectedComponents = prevSelectedComponents.filter(
|
||||
(component) => component.usageKey !== selectedComponent.usageKey,
|
||||
);
|
||||
onChangeComponentSelection?.(newSelectedComponents);
|
||||
return newSelectedComponents;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const context = useMemo<ComponentPickerContextData>(() => {
|
||||
switch (componentPickerMode) {
|
||||
case 'single':
|
||||
return {
|
||||
componentPickerMode,
|
||||
restrictToLibrary,
|
||||
onComponentSelected,
|
||||
};
|
||||
case 'multiple':
|
||||
return {
|
||||
componentPickerMode,
|
||||
restrictToLibrary,
|
||||
selectedComponents,
|
||||
addComponentToSelectedComponents,
|
||||
removeComponentFromSelectedComponents,
|
||||
};
|
||||
default:
|
||||
// istanbul ignore next: this should never happen
|
||||
throw new Error('Invalid component picker mode');
|
||||
}
|
||||
}, [
|
||||
componentPickerMode,
|
||||
restrictToLibrary,
|
||||
onComponentSelected,
|
||||
addComponentToSelectedComponents,
|
||||
removeComponentFromSelectedComponents,
|
||||
selectedComponents,
|
||||
onChangeComponentSelection,
|
||||
]);
|
||||
|
||||
return (
|
||||
<ComponentPickerContext.Provider value={context}>
|
||||
{children}
|
||||
</ComponentPickerContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export function useComponentPickerContext(): ComponentPickerContextData | NoComponentPickerType {
|
||||
const ctx = useContext(ComponentPickerContext);
|
||||
if (ctx === undefined) {
|
||||
return {
|
||||
componentPickerMode: false,
|
||||
};
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
147
src/library-authoring/common/context/LibraryContext.tsx
Normal file
147
src/library-authoring/common/context/LibraryContext.tsx
Normal file
@@ -0,0 +1,147 @@
|
||||
import { useToggle } from '@openedx/paragon';
|
||||
import {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import type { ComponentPicker } from '../../component-picker';
|
||||
import type { ContentLibrary } from '../../data/api';
|
||||
import { useContentLibrary } from '../../data/apiHooks';
|
||||
import { useComponentPickerContext } from './ComponentPickerContext';
|
||||
|
||||
export interface ComponentEditorInfo {
|
||||
usageKey: string;
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
export type LibraryContextData = {
|
||||
/** The ID of the current library */
|
||||
libraryId: string;
|
||||
libraryData?: ContentLibrary;
|
||||
readOnly: boolean;
|
||||
isLoadingLibraryData: boolean;
|
||||
collectionId: string | undefined;
|
||||
setCollectionId: (collectionId?: string) => void;
|
||||
// Only show published components
|
||||
showOnlyPublished: boolean;
|
||||
// "Create New Collection" modal
|
||||
isCreateCollectionModalOpen: boolean;
|
||||
openCreateCollectionModal: () => void;
|
||||
closeCreateCollectionModal: () => void;
|
||||
// Editor modal - for editing some component
|
||||
/** If the editor is open and the user is editing some component, this is the component being edited. */
|
||||
componentBeingEdited: ComponentEditorInfo | undefined;
|
||||
/** If an onClose callback is provided, it will be called when the editor is closed. */
|
||||
openComponentEditor: (usageKey: string, onClose?: () => void) => void;
|
||||
closeComponentEditor: () => void;
|
||||
componentPicker?: typeof ComponentPicker;
|
||||
};
|
||||
|
||||
/**
|
||||
* Library Context.
|
||||
* Always available when we're in the context of a single library.
|
||||
*
|
||||
* Get this using `useLibraryContext()`
|
||||
*
|
||||
* Not used on the "library list" on Studio home.
|
||||
*/
|
||||
const LibraryContext = createContext<LibraryContextData | undefined>(undefined);
|
||||
|
||||
type LibraryProviderProps = {
|
||||
children?: React.ReactNode;
|
||||
libraryId: string;
|
||||
/** The initial collection ID to show */
|
||||
collectionId?: string;
|
||||
showOnlyPublished?: boolean;
|
||||
/** The component picker modal to use. We need to pass it as a reference instead of
|
||||
* directly importing it to avoid the import cycle:
|
||||
* ComponentPicker > LibraryAuthoringPage/LibraryCollectionPage >
|
||||
* Sidebar > AddContentContainer > ComponentPicker */
|
||||
componentPicker?: typeof ComponentPicker;
|
||||
};
|
||||
|
||||
/**
|
||||
* React component to provide `LibraryContext`
|
||||
*/
|
||||
export const LibraryProvider = ({
|
||||
children,
|
||||
libraryId,
|
||||
collectionId: collectionIdProp,
|
||||
showOnlyPublished = false,
|
||||
componentPicker,
|
||||
}: LibraryProviderProps) => {
|
||||
const [collectionId, setCollectionId] = useState(collectionIdProp);
|
||||
const [isCreateCollectionModalOpen, openCreateCollectionModal, closeCreateCollectionModal] = useToggle(false);
|
||||
const [componentBeingEdited, setComponentBeingEdited] = useState<ComponentEditorInfo | undefined>();
|
||||
const closeComponentEditor = useCallback(() => {
|
||||
setComponentBeingEdited((prev) => {
|
||||
prev?.onClose?.();
|
||||
return undefined;
|
||||
});
|
||||
}, []);
|
||||
const openComponentEditor = useCallback((usageKey: string, onClose?: () => void) => {
|
||||
setComponentBeingEdited({ usageKey, onClose });
|
||||
}, []);
|
||||
|
||||
const { data: libraryData, isLoading: isLoadingLibraryData } = useContentLibrary(libraryId);
|
||||
|
||||
const {
|
||||
componentPickerMode,
|
||||
} = useComponentPickerContext();
|
||||
|
||||
const readOnly = !!componentPickerMode || !libraryData?.canEditLibrary;
|
||||
|
||||
const context = useMemo<LibraryContextData>(() => {
|
||||
const contextValue = {
|
||||
libraryId,
|
||||
libraryData,
|
||||
collectionId,
|
||||
setCollectionId,
|
||||
readOnly,
|
||||
isLoadingLibraryData,
|
||||
showOnlyPublished,
|
||||
isCreateCollectionModalOpen,
|
||||
openCreateCollectionModal,
|
||||
closeCreateCollectionModal,
|
||||
componentBeingEdited,
|
||||
openComponentEditor,
|
||||
closeComponentEditor,
|
||||
componentPicker,
|
||||
};
|
||||
|
||||
return contextValue;
|
||||
}, [
|
||||
libraryId,
|
||||
collectionId,
|
||||
setCollectionId,
|
||||
libraryData,
|
||||
readOnly,
|
||||
isLoadingLibraryData,
|
||||
showOnlyPublished,
|
||||
isCreateCollectionModalOpen,
|
||||
openCreateCollectionModal,
|
||||
closeCreateCollectionModal,
|
||||
componentBeingEdited,
|
||||
openComponentEditor,
|
||||
closeComponentEditor,
|
||||
componentPicker,
|
||||
]);
|
||||
|
||||
return (
|
||||
<LibraryContext.Provider value={context}>
|
||||
{children}
|
||||
</LibraryContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export function useLibraryContext(): LibraryContextData {
|
||||
const ctx = useContext(LibraryContext);
|
||||
if (ctx === undefined) {
|
||||
/* istanbul ignore next */
|
||||
throw new Error('useLibraryContext() was used in a component without a <LibraryProvider> ancestor.');
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
173
src/library-authoring/common/context/SidebarContext.tsx
Normal file
173
src/library-authoring/common/context/SidebarContext.tsx
Normal file
@@ -0,0 +1,173 @@
|
||||
import {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
export enum SidebarBodyComponentId {
|
||||
AddContent = 'add-content',
|
||||
Info = 'info',
|
||||
ComponentInfo = 'component-info',
|
||||
CollectionInfo = 'collection-info',
|
||||
}
|
||||
|
||||
export const COLLECTION_INFO_TABS = {
|
||||
Manage: 'manage',
|
||||
Details: 'details',
|
||||
} as const;
|
||||
export type CollectionInfoTab = typeof COLLECTION_INFO_TABS[keyof typeof COLLECTION_INFO_TABS];
|
||||
export const isCollectionInfoTab = (tab: string): tab is CollectionInfoTab => (
|
||||
Object.values<string>(COLLECTION_INFO_TABS).includes(tab)
|
||||
);
|
||||
|
||||
export const COMPONENT_INFO_TABS = {
|
||||
Preview: 'preview',
|
||||
Manage: 'manage',
|
||||
Details: 'details',
|
||||
} as const;
|
||||
export type ComponentInfoTab = typeof COMPONENT_INFO_TABS[keyof typeof COMPONENT_INFO_TABS];
|
||||
export const isComponentInfoTab = (tab: string): tab is ComponentInfoTab => (
|
||||
Object.values<string>(COMPONENT_INFO_TABS).includes(tab)
|
||||
);
|
||||
|
||||
export interface SidebarComponentInfo {
|
||||
type: SidebarBodyComponentId;
|
||||
id: string;
|
||||
/** Additional action on Sidebar display */
|
||||
additionalAction?: SidebarAdditionalActions;
|
||||
/** Current tab in the sidebar */
|
||||
currentTab?: CollectionInfoTab | ComponentInfoTab;
|
||||
}
|
||||
|
||||
export enum SidebarAdditionalActions {
|
||||
JumpToAddCollections = 'jump-to-add-collections',
|
||||
}
|
||||
|
||||
export type SidebarContextData = {
|
||||
closeLibrarySidebar: () => void;
|
||||
openAddContentSidebar: () => void;
|
||||
openInfoSidebar: () => void;
|
||||
openCollectionInfoSidebar: (collectionId: string, additionalAction?: SidebarAdditionalActions) => void;
|
||||
openComponentInfoSidebar: (usageKey: string, additionalAction?: SidebarAdditionalActions) => void;
|
||||
sidebarComponentInfo?: SidebarComponentInfo;
|
||||
resetSidebarAdditionalActions: () => void;
|
||||
setSidebarCurrentTab: (tab: CollectionInfoTab | ComponentInfoTab) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sidebar Context.
|
||||
*
|
||||
* Get this using `useSidebarContext()`
|
||||
*
|
||||
*/
|
||||
const SidebarContext = createContext<SidebarContextData | undefined>(undefined);
|
||||
|
||||
type SidebarProviderProps = {
|
||||
children?: React.ReactNode;
|
||||
/** Only used for testing */
|
||||
initialSidebarComponentInfo?: SidebarComponentInfo;
|
||||
};
|
||||
|
||||
/**
|
||||
* React component to provide `LibraryContext`
|
||||
*/
|
||||
export const SidebarProvider = ({
|
||||
children,
|
||||
initialSidebarComponentInfo,
|
||||
}: SidebarProviderProps) => {
|
||||
const [sidebarComponentInfo, setSidebarComponentInfo] = useState<SidebarComponentInfo | undefined>(
|
||||
initialSidebarComponentInfo,
|
||||
);
|
||||
|
||||
/** Helper function to consume addtional action once performed.
|
||||
Required to redo the action.
|
||||
*/
|
||||
const resetSidebarAdditionalActions = useCallback(() => {
|
||||
setSidebarComponentInfo((prev) => (prev && { ...prev, additionalAction: undefined }));
|
||||
}, []);
|
||||
|
||||
const closeLibrarySidebar = useCallback(() => {
|
||||
setSidebarComponentInfo(undefined);
|
||||
}, []);
|
||||
const openAddContentSidebar = useCallback(() => {
|
||||
setSidebarComponentInfo({ id: '', type: SidebarBodyComponentId.AddContent });
|
||||
}, []);
|
||||
const openInfoSidebar = useCallback(() => {
|
||||
setSidebarComponentInfo({ id: '', type: SidebarBodyComponentId.Info });
|
||||
}, []);
|
||||
|
||||
const openComponentInfoSidebar = useCallback((usageKey: string, additionalAction?: SidebarAdditionalActions) => {
|
||||
setSidebarComponentInfo((prev) => ({
|
||||
...prev,
|
||||
id: usageKey,
|
||||
type: SidebarBodyComponentId.ComponentInfo,
|
||||
additionalAction,
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const openCollectionInfoSidebar = useCallback((
|
||||
newCollectionId: string,
|
||||
additionalAction?: SidebarAdditionalActions,
|
||||
) => {
|
||||
setSidebarComponentInfo((prev) => ({
|
||||
...prev,
|
||||
id: newCollectionId,
|
||||
type: SidebarBodyComponentId.CollectionInfo,
|
||||
additionalAction,
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const setSidebarCurrentTab = useCallback((tab: CollectionInfoTab | ComponentInfoTab) => {
|
||||
setSidebarComponentInfo((prev) => (prev && { ...prev, currentTab: tab }));
|
||||
}, []);
|
||||
|
||||
const context = useMemo<SidebarContextData>(() => {
|
||||
const contextValue = {
|
||||
closeLibrarySidebar,
|
||||
openAddContentSidebar,
|
||||
openInfoSidebar,
|
||||
openComponentInfoSidebar,
|
||||
sidebarComponentInfo,
|
||||
openCollectionInfoSidebar,
|
||||
resetSidebarAdditionalActions,
|
||||
setSidebarCurrentTab,
|
||||
};
|
||||
|
||||
return contextValue;
|
||||
}, [
|
||||
closeLibrarySidebar,
|
||||
openAddContentSidebar,
|
||||
openInfoSidebar,
|
||||
openComponentInfoSidebar,
|
||||
sidebarComponentInfo,
|
||||
openCollectionInfoSidebar,
|
||||
resetSidebarAdditionalActions,
|
||||
setSidebarCurrentTab,
|
||||
]);
|
||||
|
||||
return (
|
||||
<SidebarContext.Provider value={context}>
|
||||
{children}
|
||||
</SidebarContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export function useSidebarContext(): SidebarContextData {
|
||||
const ctx = useContext(SidebarContext);
|
||||
if (ctx === undefined) {
|
||||
/* istanbul ignore next */
|
||||
return {
|
||||
closeLibrarySidebar: () => {},
|
||||
openAddContentSidebar: () => {},
|
||||
openInfoSidebar: () => {},
|
||||
openComponentInfoSidebar: () => {},
|
||||
openCollectionInfoSidebar: () => {},
|
||||
resetSidebarAdditionalActions: () => {},
|
||||
setSidebarCurrentTab: () => {},
|
||||
sidebarComponentInfo: undefined,
|
||||
};
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
@@ -10,14 +10,16 @@ import { FormattedMessage, FormattedNumber, useIntl } from '@edx/frontend-platfo
|
||||
|
||||
import { LoadingSpinner } from '../../generic/Loading';
|
||||
import DeleteModal from '../../generic/delete-modal/DeleteModal';
|
||||
import { useLibraryContext } from '../common/context';
|
||||
import { useLibraryContext } from '../common/context/LibraryContext';
|
||||
import { useSidebarContext } from '../common/context/SidebarContext';
|
||||
import { getXBlockAssetsApiUrl } from '../data/api';
|
||||
import { useDeleteXBlockAsset, useInvalidateXBlockAssets, useXBlockAssets } from '../data/apiHooks';
|
||||
import messages from './messages';
|
||||
|
||||
export const ComponentAdvancedAssets: React.FC<Record<never, never>> = () => {
|
||||
const intl = useIntl();
|
||||
const { readOnly, sidebarComponentInfo } = useLibraryContext();
|
||||
const { readOnly } = useLibraryContext();
|
||||
const { sidebarComponentInfo } = useSidebarContext();
|
||||
|
||||
const usageKey = sidebarComponentInfo?.id;
|
||||
// istanbul ignore if: this should never happen in production
|
||||
|
||||
@@ -12,8 +12,9 @@ import {
|
||||
mockXBlockAssets,
|
||||
mockXBlockOLX,
|
||||
} from '../data/api.mocks';
|
||||
import { LibraryProvider } from '../common/context/LibraryContext';
|
||||
import { SidebarBodyComponentId, SidebarProvider } from '../common/context/SidebarContext';
|
||||
import * as apiHooks from '../data/apiHooks';
|
||||
import { LibraryProvider, SidebarBodyComponentId } from '../common/context';
|
||||
import { ComponentAdvancedInfo } from './ComponentAdvancedInfo';
|
||||
import { getXBlockAssetsApiUrl } from '../data/api';
|
||||
|
||||
@@ -31,15 +32,15 @@ const render = (
|
||||
<ComponentAdvancedInfo />,
|
||||
{
|
||||
extraWrapper: ({ children }: { children: React.ReactNode }) => (
|
||||
<LibraryProvider
|
||||
libraryId={libraryId}
|
||||
initialSidebarComponentInfo={{
|
||||
id: usageKey,
|
||||
type: SidebarBodyComponentId.ComponentInfo,
|
||||
}}
|
||||
showOnlyPublished={showOnlyPublished}
|
||||
>
|
||||
{children}
|
||||
<LibraryProvider libraryId={libraryId} showOnlyPublished={showOnlyPublished}>
|
||||
<SidebarProvider
|
||||
initialSidebarComponentInfo={{
|
||||
id: usageKey,
|
||||
type: SidebarBodyComponentId.ComponentInfo,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</SidebarProvider>
|
||||
</LibraryProvider>
|
||||
),
|
||||
},
|
||||
|
||||
@@ -11,7 +11,8 @@ import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { LoadingSpinner } from '../../generic/Loading';
|
||||
import { CodeEditor, EditorAccessor } from '../../generic/CodeEditor';
|
||||
import { useLibraryContext } from '../common/context';
|
||||
import { useLibraryContext } from '../common/context/LibraryContext';
|
||||
import { useSidebarContext } from '../common/context/SidebarContext';
|
||||
import {
|
||||
useUpdateXBlockOLX,
|
||||
useXBlockOLX,
|
||||
@@ -21,11 +22,8 @@ import { ComponentAdvancedAssets } from './ComponentAdvancedAssets';
|
||||
|
||||
const ComponentAdvancedInfoInner: React.FC<Record<never, never>> = () => {
|
||||
const intl = useIntl();
|
||||
const {
|
||||
readOnly,
|
||||
sidebarComponentInfo,
|
||||
showOnlyPublished,
|
||||
} = useLibraryContext();
|
||||
const { readOnly, showOnlyPublished } = useLibraryContext();
|
||||
const { sidebarComponentInfo } = useSidebarContext();
|
||||
|
||||
const usageKey = sidebarComponentInfo?.id;
|
||||
// istanbul ignore if: this should never happen in production
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
mockXBlockAssets,
|
||||
mockXBlockOLX,
|
||||
} from '../data/api.mocks';
|
||||
import { LibraryProvider, SidebarBodyComponentId } from '../common/context';
|
||||
import { SidebarBodyComponentId, SidebarProvider } from '../common/context/SidebarContext';
|
||||
import ComponentDetails from './ComponentDetails';
|
||||
|
||||
mockContentLibrary.applyMock();
|
||||
@@ -17,19 +17,16 @@ mockLibraryBlockMetadata.applyMock();
|
||||
mockXBlockAssets.applyMock();
|
||||
mockXBlockOLX.applyMock();
|
||||
|
||||
const { libraryId: mockLibraryId } = mockContentLibrary;
|
||||
|
||||
const render = (usageKey: string) => baseRender(<ComponentDetails />, {
|
||||
extraWrapper: ({ children }) => (
|
||||
<LibraryProvider
|
||||
libraryId={mockLibraryId}
|
||||
<SidebarProvider
|
||||
initialSidebarComponentInfo={{
|
||||
id: usageKey,
|
||||
type: SidebarBodyComponentId.ComponentInfo,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</LibraryProvider>
|
||||
</SidebarProvider>
|
||||
),
|
||||
});
|
||||
|
||||
|
||||
@@ -3,14 +3,14 @@ import { Stack } from '@openedx/paragon';
|
||||
|
||||
import AlertError from '../../generic/alert-error';
|
||||
import Loading from '../../generic/Loading';
|
||||
import { useLibraryContext } from '../common/context';
|
||||
import { useSidebarContext } from '../common/context/SidebarContext';
|
||||
import { useLibraryBlockMetadata } from '../data/apiHooks';
|
||||
import HistoryWidget from '../generic/history-widget';
|
||||
import { ComponentAdvancedInfo } from './ComponentAdvancedInfo';
|
||||
import messages from './messages';
|
||||
|
||||
const ComponentDetails = () => {
|
||||
const { sidebarComponentInfo } = useLibraryContext();
|
||||
const { sidebarComponentInfo } = useSidebarContext();
|
||||
|
||||
const usageKey = sidebarComponentInfo?.id;
|
||||
|
||||
|
||||
@@ -6,7 +6,8 @@ import {
|
||||
} from '../../testUtils';
|
||||
import { mockContentLibrary, mockLibraryBlockMetadata } from '../data/api.mocks';
|
||||
import { mockBroadcastChannel } from '../../generic/data/api.mock';
|
||||
import { LibraryProvider, SidebarBodyComponentId } from '../common/context';
|
||||
import { LibraryProvider } from '../common/context/LibraryContext';
|
||||
import { SidebarBodyComponentId, SidebarProvider } from '../common/context/SidebarContext';
|
||||
import ComponentInfo from './ComponentInfo';
|
||||
import { getXBlockPublishApiUrl } from '../data/api';
|
||||
|
||||
@@ -24,14 +25,15 @@ jest.mock('./ComponentManagement', () => ({
|
||||
|
||||
const withLibraryId = (libraryId: string, sidebarComponentUsageKey: string) => ({
|
||||
extraWrapper: ({ children }: { children: React.ReactNode }) => (
|
||||
<LibraryProvider
|
||||
libraryId={libraryId}
|
||||
initialSidebarComponentInfo={{
|
||||
id: sidebarComponentUsageKey,
|
||||
type: SidebarBodyComponentId.ComponentInfo,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
<LibraryProvider libraryId={libraryId}>
|
||||
<SidebarProvider
|
||||
initialSidebarComponentInfo={{
|
||||
id: sidebarComponentUsageKey,
|
||||
type: SidebarBodyComponentId.ComponentInfo,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</SidebarProvider>
|
||||
</LibraryProvider>
|
||||
),
|
||||
});
|
||||
|
||||
@@ -11,13 +11,15 @@ import {
|
||||
CheckBoxOutlineBlank,
|
||||
} from '@openedx/paragon/icons';
|
||||
|
||||
import { useComponentPickerContext } from '../common/context/ComponentPickerContext';
|
||||
import { useLibraryContext } from '../common/context/LibraryContext';
|
||||
import {
|
||||
SidebarAdditionalActions,
|
||||
useLibraryContext,
|
||||
type ComponentInfoTab,
|
||||
COMPONENT_INFO_TABS,
|
||||
ComponentInfoTab,
|
||||
SidebarAdditionalActions,
|
||||
isComponentInfoTab,
|
||||
} from '../common/context';
|
||||
useSidebarContext,
|
||||
} from '../common/context/SidebarContext';
|
||||
import ComponentMenu from '../components';
|
||||
import { canEditComponent } from '../components/ComponentEditorModal';
|
||||
import ComponentDetails from './ComponentDetails';
|
||||
@@ -31,14 +33,15 @@ import { ToastContext } from '../../generic/toast-context';
|
||||
const AddComponentWidget = () => {
|
||||
const intl = useIntl();
|
||||
|
||||
const { sidebarComponentInfo } = useSidebarContext();
|
||||
|
||||
const {
|
||||
sidebarComponentInfo,
|
||||
componentPickerMode,
|
||||
onComponentSelected,
|
||||
addComponentToSelectedComponents,
|
||||
removeComponentFromSelectedComponents,
|
||||
selectedComponents,
|
||||
} = useLibraryContext();
|
||||
} = useComponentPickerContext();
|
||||
|
||||
const usageKey = sidebarComponentInfo?.id;
|
||||
|
||||
@@ -97,13 +100,8 @@ const AddComponentWidget = () => {
|
||||
const ComponentInfo = () => {
|
||||
const intl = useIntl();
|
||||
|
||||
const {
|
||||
sidebarComponentInfo,
|
||||
readOnly,
|
||||
openComponentEditor,
|
||||
resetSidebarAdditionalActions,
|
||||
setSidebarCurrentTab,
|
||||
} = useLibraryContext();
|
||||
const { readOnly, openComponentEditor } = useLibraryContext();
|
||||
const { setSidebarCurrentTab, sidebarComponentInfo, resetSidebarAdditionalActions } = useSidebarContext();
|
||||
|
||||
const jumpToCollections = sidebarComponentInfo?.additionalAction === SidebarAdditionalActions.JumpToAddCollections;
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@ import {
|
||||
} from '../../testUtils';
|
||||
import { mockContentLibrary } from '../data/api.mocks';
|
||||
import { getXBlockFieldsVersionApiUrl, getXBlockFieldsApiUrl } from '../data/api';
|
||||
import { LibraryProvider, SidebarBodyComponentId } from '../common/context';
|
||||
import { LibraryProvider } from '../common/context/LibraryContext';
|
||||
import { SidebarBodyComponentId, SidebarProvider } from '../common/context/SidebarContext';
|
||||
import ComponentInfoHeader from './ComponentInfoHeader';
|
||||
|
||||
const { libraryId: mockLibraryId, libraryIdReadOnly } = mockContentLibrary;
|
||||
@@ -24,14 +25,15 @@ const xBlockFields = {
|
||||
|
||||
const render = (libraryId: string = mockLibraryId) => baseRender(<ComponentInfoHeader />, {
|
||||
extraWrapper: ({ children }) => (
|
||||
<LibraryProvider
|
||||
libraryId={libraryId}
|
||||
initialSidebarComponentInfo={{
|
||||
id: usageKey,
|
||||
type: SidebarBodyComponentId.ComponentInfo,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
<LibraryProvider libraryId={libraryId}>
|
||||
<SidebarProvider
|
||||
initialSidebarComponentInfo={{
|
||||
id: usageKey,
|
||||
type: SidebarBodyComponentId.ComponentInfo,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</SidebarProvider>
|
||||
</LibraryProvider>
|
||||
),
|
||||
});
|
||||
|
||||
@@ -9,7 +9,8 @@ import {
|
||||
import { Edit } from '@openedx/paragon/icons';
|
||||
|
||||
import { ToastContext } from '../../generic/toast-context';
|
||||
import { useLibraryContext } from '../common/context';
|
||||
import { useLibraryContext } from '../common/context/LibraryContext';
|
||||
import { useSidebarContext } from '../common/context/SidebarContext';
|
||||
import { useUpdateXBlockFields, useXBlockFields } from '../data/apiHooks';
|
||||
import messages from './messages';
|
||||
|
||||
@@ -17,11 +18,8 @@ const ComponentInfoHeader = () => {
|
||||
const intl = useIntl();
|
||||
const [inputIsActive, setIsActive] = useState(false);
|
||||
|
||||
const {
|
||||
sidebarComponentInfo,
|
||||
readOnly,
|
||||
showOnlyPublished,
|
||||
} = useLibraryContext();
|
||||
const { readOnly, showOnlyPublished } = useLibraryContext();
|
||||
const { sidebarComponentInfo } = useSidebarContext();
|
||||
|
||||
const usageKey = sidebarComponentInfo?.id;
|
||||
// istanbul ignore next
|
||||
|
||||
@@ -7,7 +7,8 @@ import {
|
||||
screen,
|
||||
waitFor,
|
||||
} from '../../testUtils';
|
||||
import { LibraryProvider, SidebarBodyComponentId } from '../common/context';
|
||||
import { LibraryProvider } from '../common/context/LibraryContext';
|
||||
import { SidebarBodyComponentId, SidebarProvider } from '../common/context/SidebarContext';
|
||||
import { mockContentLibrary, mockLibraryBlockMetadata } from '../data/api.mocks';
|
||||
import ComponentManagement from './ComponentManagement';
|
||||
|
||||
@@ -37,14 +38,15 @@ const matchInnerText = (nodeName: string, textToMatch: string) => (_: string, el
|
||||
|
||||
const render = (usageKey: string, libraryId?: string) => baseRender(<ComponentManagement />, {
|
||||
extraWrapper: ({ children }) => (
|
||||
<LibraryProvider
|
||||
libraryId={libraryId || mockContentLibrary.libraryId}
|
||||
initialSidebarComponentInfo={{
|
||||
id: usageKey,
|
||||
type: SidebarBodyComponentId.ComponentInfo,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
<LibraryProvider libraryId={libraryId || mockContentLibrary.libraryId}>
|
||||
<SidebarProvider
|
||||
initialSidebarComponentInfo={{
|
||||
id: usageKey,
|
||||
type: SidebarBodyComponentId.ComponentInfo,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</SidebarProvider>
|
||||
</LibraryProvider>
|
||||
),
|
||||
});
|
||||
|
||||
@@ -6,7 +6,8 @@ import {
|
||||
BookOpen, ExpandLess, ExpandMore, Tag,
|
||||
} from '@openedx/paragon/icons';
|
||||
|
||||
import { SidebarAdditionalActions, useLibraryContext } from '../common/context';
|
||||
import { useLibraryContext } from '../common/context/LibraryContext';
|
||||
import { SidebarAdditionalActions, useSidebarContext } from '../common/context/SidebarContext';
|
||||
import { useLibraryBlockMetadata } from '../data/apiHooks';
|
||||
import StatusWidget from '../generic/status-widget';
|
||||
import messages from './messages';
|
||||
@@ -16,9 +17,8 @@ import ManageCollections from './ManageCollections';
|
||||
|
||||
const ComponentManagement = () => {
|
||||
const intl = useIntl();
|
||||
const {
|
||||
sidebarComponentInfo, readOnly, resetSidebarAdditionalActions, isLoadingLibraryData,
|
||||
} = useLibraryContext();
|
||||
const { readOnly, isLoadingLibraryData } = useLibraryContext();
|
||||
const { sidebarComponentInfo, resetSidebarAdditionalActions } = useSidebarContext();
|
||||
const jumpToCollections = sidebarComponentInfo?.additionalAction === SidebarAdditionalActions.JumpToAddCollections;
|
||||
const [tagsCollapseIsOpen, setTagsCollapseOpen] = React.useState(!jumpToCollections);
|
||||
const [collectionsCollapseIsOpen, setCollectionsCollapseOpen] = React.useState(true);
|
||||
|
||||
@@ -4,7 +4,8 @@ import {
|
||||
render as baseRender,
|
||||
screen,
|
||||
} from '../../testUtils';
|
||||
import { LibraryProvider, SidebarBodyComponentId } from '../common/context';
|
||||
import { LibraryProvider } from '../common/context/LibraryContext';
|
||||
import { SidebarBodyComponentId, SidebarProvider } from '../common/context/SidebarContext';
|
||||
import { mockContentLibrary, mockLibraryBlockMetadata } from '../data/api.mocks';
|
||||
import ComponentPreview from './ComponentPreview';
|
||||
|
||||
@@ -19,14 +20,15 @@ const usageKey = mockLibraryBlockMetadata.usageKeyPublished;
|
||||
|
||||
const render = () => baseRender(<ComponentPreview />, {
|
||||
extraWrapper: ({ children }) => (
|
||||
<LibraryProvider
|
||||
libraryId={libraryId}
|
||||
initialSidebarComponentInfo={{
|
||||
id: usageKey,
|
||||
type: SidebarBodyComponentId.ComponentInfo,
|
||||
}}
|
||||
>
|
||||
{ children }
|
||||
<LibraryProvider libraryId={libraryId}>
|
||||
<SidebarProvider
|
||||
initialSidebarComponentInfo={{
|
||||
id: usageKey,
|
||||
type: SidebarBodyComponentId.ComponentInfo,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</SidebarProvider>
|
||||
</LibraryProvider>
|
||||
),
|
||||
});
|
||||
|
||||
@@ -2,7 +2,8 @@ import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Button, StandardModal, useToggle } from '@openedx/paragon';
|
||||
import { OpenInFull } from '@openedx/paragon/icons';
|
||||
|
||||
import { useLibraryContext } from '../common/context';
|
||||
import { useLibraryContext } from '../common/context/LibraryContext';
|
||||
import { useSidebarContext } from '../common/context/SidebarContext';
|
||||
import { LibraryBlock } from '../LibraryBlock';
|
||||
import messages from './messages';
|
||||
import { useLibraryBlockMetadata } from '../data/apiHooks';
|
||||
@@ -37,7 +38,8 @@ const ComponentPreview = () => {
|
||||
const intl = useIntl();
|
||||
|
||||
const [isModalOpen, openModal, closeModal] = useToggle();
|
||||
const { sidebarComponentInfo, showOnlyPublished } = useLibraryContext();
|
||||
const { showOnlyPublished } = useLibraryContext();
|
||||
const { sidebarComponentInfo } = useSidebarContext();
|
||||
|
||||
const usageKey = sidebarComponentInfo?.id;
|
||||
// istanbul ignore if: this should never happen
|
||||
|
||||
@@ -12,7 +12,7 @@ import mockCollectionsResults from '../__mocks__/collection-search.json';
|
||||
import { mockContentSearchConfig } from '../../search-manager/data/api.mock';
|
||||
import { mockContentLibrary, mockLibraryBlockMetadata } from '../data/api.mocks';
|
||||
import ManageCollections from './ManageCollections';
|
||||
import { LibraryProvider } from '../common/context';
|
||||
import { LibraryProvider } from '../common/context/LibraryContext';
|
||||
import { getLibraryBlockCollectionsUrl } from '../data/api';
|
||||
|
||||
let axiosMock: MockAdapter;
|
||||
@@ -24,7 +24,9 @@ mockContentSearchConfig.applyMock();
|
||||
|
||||
const render = (ui: React.ReactElement) => baseRender(ui, {
|
||||
extraWrapper: ({ children }) => (
|
||||
<LibraryProvider libraryId={mockContentLibrary.libraryId}>{children}</LibraryProvider>
|
||||
<LibraryProvider libraryId={mockContentLibrary.libraryId}>
|
||||
{children}
|
||||
</LibraryProvider>
|
||||
),
|
||||
});
|
||||
|
||||
|
||||
@@ -15,7 +15,8 @@ import messages from './messages';
|
||||
import { useUpdateComponentCollections } from '../data/apiHooks';
|
||||
import { ToastContext } from '../../generic/toast-context';
|
||||
import { CollectionMetadata } from '../data/api';
|
||||
import { SidebarAdditionalActions, useLibraryContext } from '../common/context';
|
||||
import { useLibraryContext } from '../common/context/LibraryContext';
|
||||
import { SidebarAdditionalActions, useSidebarContext } from '../common/context/SidebarContext';
|
||||
|
||||
interface ManageCollectionsProps {
|
||||
usageKey: string;
|
||||
@@ -190,7 +191,7 @@ const ComponentCollections = ({ collections, onManageClick }: {
|
||||
};
|
||||
|
||||
const ManageCollections = ({ usageKey, collections }: ManageCollectionsProps) => {
|
||||
const { sidebarComponentInfo, resetSidebarAdditionalActions } = useLibraryContext();
|
||||
const { sidebarComponentInfo, resetSidebarAdditionalActions } = useSidebarContext();
|
||||
const jumpToCollections = sidebarComponentInfo?.additionalAction === SidebarAdditionalActions.JumpToAddCollections;
|
||||
const [editing, setEditing] = useState(jumpToCollections);
|
||||
const collectionNames = collections.map((collection) => collection.title);
|
||||
|
||||
@@ -6,9 +6,10 @@ import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
type ComponentSelectedEvent,
|
||||
type ComponentSelectionChangedEvent,
|
||||
LibraryProvider,
|
||||
useLibraryContext,
|
||||
} from '../common/context';
|
||||
ComponentPickerProvider,
|
||||
} from '../common/context/ComponentPickerContext';
|
||||
import { LibraryProvider, useLibraryContext } from '../common/context/LibraryContext';
|
||||
import { SidebarProvider } from '../common/context/SidebarContext';
|
||||
import LibraryAuthoringPage from '../LibraryAuthoringPage';
|
||||
import LibraryCollectionPage from '../collections/LibraryCollectionPage';
|
||||
import SelectLibrary from './SelectLibrary';
|
||||
@@ -81,7 +82,7 @@ export const ComponentPicker: React.FC<ComponentPickerProps> = ({
|
||||
|
||||
const restrictToLibrary = !!libraryId;
|
||||
|
||||
const libraryProviderProps = componentPickerMode === 'single' ? {
|
||||
const componentPickerProviderProps = componentPickerMode === 'single' ? {
|
||||
componentPickerMode,
|
||||
onComponentSelected,
|
||||
restrictToLibrary,
|
||||
@@ -100,19 +101,22 @@ export const ComponentPicker: React.FC<ComponentPickerProps> = ({
|
||||
</Stepper.Step>
|
||||
|
||||
<Stepper.Step eventKey="pick-components" title="Pick some components">
|
||||
<LibraryProvider
|
||||
libraryId={selectedLibrary}
|
||||
showOnlyPublished={calcShowOnlyPublished}
|
||||
{...libraryProviderProps}
|
||||
>
|
||||
{ calcShowOnlyPublished
|
||||
&& (
|
||||
<Alert variant="info" className="m-2">
|
||||
<FormattedMessage {...messages.pickerInfoBanner} />
|
||||
</Alert>
|
||||
)}
|
||||
<InnerComponentPicker returnToLibrarySelection={returnToLibrarySelection} />
|
||||
</LibraryProvider>
|
||||
<ComponentPickerProvider {...componentPickerProviderProps}>
|
||||
<LibraryProvider
|
||||
libraryId={selectedLibrary}
|
||||
showOnlyPublished={calcShowOnlyPublished}
|
||||
>
|
||||
<SidebarProvider>
|
||||
{ calcShowOnlyPublished
|
||||
&& (
|
||||
<Alert variant="info" className="m-2">
|
||||
<FormattedMessage {...messages.pickerInfoBanner} />
|
||||
</Alert>
|
||||
)}
|
||||
<InnerComponentPicker returnToLibrarySelection={returnToLibrarySelection} />
|
||||
</SidebarProvider>
|
||||
</LibraryProvider>
|
||||
</ComponentPickerProvider>
|
||||
</Stepper.Step>
|
||||
</Stepper>
|
||||
);
|
||||
|
||||
@@ -4,7 +4,7 @@ import type MockAdapter from 'axios-mock-adapter';
|
||||
import {
|
||||
initializeMocks, render as baseRender, screen, waitFor, waitForElementToBeRemoved, within,
|
||||
} from '../../testUtils';
|
||||
import { LibraryProvider } from '../common/context';
|
||||
import { LibraryProvider } from '../common/context/LibraryContext';
|
||||
import { type CollectionHit } from '../../search-manager';
|
||||
import CollectionCard from './CollectionCard';
|
||||
import messages from './messages';
|
||||
@@ -42,7 +42,7 @@ const render = (ui: React.ReactElement, showOnlyPublished: boolean = false) => b
|
||||
libraryId="lib:Axim:TEST"
|
||||
showOnlyPublished={showOnlyPublished}
|
||||
>
|
||||
{ children }
|
||||
{children}
|
||||
</LibraryProvider>
|
||||
),
|
||||
});
|
||||
@@ -81,7 +81,7 @@ describe('<CollectionCard />', () => {
|
||||
const openMenuItem = screen.getByRole('link', { name: 'Open' });
|
||||
expect(openMenuItem).toBeInTheDocument();
|
||||
|
||||
expect(openMenuItem).toHaveAttribute('href', '/library/lb:org1:Demo_Course/collection/collection-display-name/');
|
||||
expect(openMenuItem).toHaveAttribute('href', '/library/lb:org1:Demo_Course/collection/collection-display-name');
|
||||
});
|
||||
|
||||
it('should show confirmation box, delete collection and show toast to undo deletion', async () => {
|
||||
|
||||
@@ -11,7 +11,9 @@ import { MoreVert } from '@openedx/paragon/icons';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { type CollectionHit } from '../../search-manager';
|
||||
import { useLibraryContext } from '../common/context';
|
||||
import { useComponentPickerContext } from '../common/context/ComponentPickerContext';
|
||||
import { useLibraryContext } from '../common/context/LibraryContext';
|
||||
import { useSidebarContext } from '../common/context/SidebarContext';
|
||||
import BaseComponentCard from './BaseComponentCard';
|
||||
import { ToastContext } from '../../generic/toast-context';
|
||||
import { useDeleteCollection, useRestoreCollection } from '../data/apiHooks';
|
||||
@@ -26,7 +28,7 @@ const CollectionMenu = ({ collectionHit } : CollectionMenuProps) => {
|
||||
const intl = useIntl();
|
||||
const { showToast } = useContext(ToastContext);
|
||||
const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useToggle(false);
|
||||
const { closeLibrarySidebar, sidebarComponentInfo } = useLibraryContext();
|
||||
const { closeLibrarySidebar, sidebarComponentInfo } = useSidebarContext();
|
||||
|
||||
const restoreCollectionMutation = useRestoreCollection(collectionHit.contextKey, collectionHit.blockId);
|
||||
const restoreCollection = useCallback(() => {
|
||||
@@ -76,7 +78,7 @@ const CollectionMenu = ({ collectionHit } : CollectionMenuProps) => {
|
||||
<Dropdown.Menu>
|
||||
<Dropdown.Item
|
||||
as={Link}
|
||||
to={`/library/${collectionHit.contextKey}/collection/${collectionHit.blockId}/`}
|
||||
to={`/library/${collectionHit.contextKey}/collection/${collectionHit.blockId}`}
|
||||
>
|
||||
<FormattedMessage {...messages.menuOpen} />
|
||||
</Dropdown.Item>
|
||||
@@ -104,11 +106,9 @@ type CollectionCardProps = {
|
||||
};
|
||||
|
||||
const CollectionCard = ({ collectionHit } : CollectionCardProps) => {
|
||||
const {
|
||||
openCollectionInfoSidebar,
|
||||
componentPickerMode,
|
||||
showOnlyPublished,
|
||||
} = useLibraryContext();
|
||||
const { componentPickerMode } = useComponentPickerContext();
|
||||
const { showOnlyPublished } = useLibraryContext();
|
||||
const { openCollectionInfoSidebar } = useSidebarContext();
|
||||
|
||||
const {
|
||||
type: componentType,
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
waitFor,
|
||||
initializeMocks,
|
||||
} from '../../testUtils';
|
||||
import { LibraryProvider } from '../common/context';
|
||||
import { LibraryProvider } from '../common/context/LibraryContext';
|
||||
import { getClipboardUrl } from '../../generic/data/api';
|
||||
import { ContentHit } from '../../search-manager';
|
||||
import ComponentCard from './ComponentCard';
|
||||
@@ -46,7 +46,11 @@ const clipboardBroadcastChannelMock = {
|
||||
|
||||
const libraryId = 'lib:org1:Demo_Course';
|
||||
const render = () => baseRender(<ComponentCard contentHit={contentHit} />, {
|
||||
extraWrapper: ({ children }) => <LibraryProvider libraryId={libraryId}>{ children }</LibraryProvider>,
|
||||
extraWrapper: ({ children }) => (
|
||||
<LibraryProvider libraryId={libraryId}>
|
||||
{ children }
|
||||
</LibraryProvider>
|
||||
),
|
||||
});
|
||||
|
||||
describe('<ComponentCard />', () => {
|
||||
|
||||
@@ -19,7 +19,9 @@ import { STUDIO_CLIPBOARD_CHANNEL } from '../../constants';
|
||||
import { updateClipboard } from '../../generic/data/api';
|
||||
import { ToastContext } from '../../generic/toast-context';
|
||||
import { type ContentHit } from '../../search-manager';
|
||||
import { SidebarAdditionalActions, useLibraryContext } from '../common/context';
|
||||
import { useComponentPickerContext } from '../common/context/ComponentPickerContext';
|
||||
import { useLibraryContext } from '../common/context/LibraryContext';
|
||||
import { SidebarAdditionalActions, useSidebarContext } from '../common/context/SidebarContext';
|
||||
import { useRemoveComponentsFromCollection } from '../data/apiHooks';
|
||||
import BaseComponentCard from './BaseComponentCard';
|
||||
import { canEditComponent } from './ComponentEditorModal';
|
||||
@@ -35,11 +37,14 @@ export const ComponentMenu = ({ usageKey }: { usageKey: string }) => {
|
||||
const {
|
||||
libraryId,
|
||||
collectionId,
|
||||
openComponentEditor,
|
||||
} = useLibraryContext();
|
||||
|
||||
const {
|
||||
sidebarComponentInfo,
|
||||
openComponentInfoSidebar,
|
||||
openComponentEditor,
|
||||
closeLibrarySidebar,
|
||||
} = useLibraryContext();
|
||||
} = useSidebarContext();
|
||||
|
||||
const canEdit = usageKey && canEditComponent(usageKey);
|
||||
const { showToast } = useContext(ToastContext);
|
||||
@@ -121,7 +126,7 @@ const AddComponentWidget = ({ usageKey, blockType }: AddComponentWidgetProps) =>
|
||||
addComponentToSelectedComponents,
|
||||
removeComponentFromSelectedComponents,
|
||||
selectedComponents,
|
||||
} = useLibraryContext();
|
||||
} = useComponentPickerContext();
|
||||
|
||||
// istanbul ignore if: this should never happen
|
||||
if (!usageKey) {
|
||||
@@ -178,11 +183,9 @@ const AddComponentWidget = ({ usageKey, blockType }: AddComponentWidgetProps) =>
|
||||
};
|
||||
|
||||
const ComponentCard = ({ contentHit }: ComponentCardProps) => {
|
||||
const {
|
||||
openComponentInfoSidebar,
|
||||
componentPickerMode,
|
||||
showOnlyPublished,
|
||||
} = useLibraryContext();
|
||||
const { showOnlyPublished } = useLibraryContext();
|
||||
const { openComponentInfoSidebar } = useSidebarContext();
|
||||
const { componentPickerMode } = useComponentPickerContext();
|
||||
|
||||
const {
|
||||
blockType,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { getLibraryId } from '../../generic/key-utils';
|
||||
import {
|
||||
fireEvent,
|
||||
render,
|
||||
@@ -6,7 +5,7 @@ import {
|
||||
initializeMocks,
|
||||
waitFor,
|
||||
} from '../../testUtils';
|
||||
import { LibraryProvider } from '../common/context';
|
||||
import { SidebarProvider } from '../common/context/SidebarContext';
|
||||
import { mockContentLibrary, mockDeleteLibraryBlock, mockLibraryBlockMetadata } from '../data/api.mocks';
|
||||
import ComponentDeleter from './ComponentDeleter';
|
||||
|
||||
@@ -17,9 +16,7 @@ const mockDelete = mockDeleteLibraryBlock.applyMock();
|
||||
const usageKey = mockLibraryBlockMetadata.usageKeyPublished;
|
||||
|
||||
const renderArgs = {
|
||||
extraWrapper: ({ children }: { children: React.ReactNode }) => (
|
||||
<LibraryProvider libraryId={getLibraryId(usageKey)}>{children}</LibraryProvider>
|
||||
),
|
||||
extraWrapper: SidebarProvider,
|
||||
};
|
||||
|
||||
describe('<ComponentDeleter />', () => {
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from '@openedx/paragon';
|
||||
import { Warning } from '@openedx/paragon/icons';
|
||||
|
||||
import { useLibraryContext } from '../common/context';
|
||||
import { useSidebarContext } from '../common/context/SidebarContext';
|
||||
import { useDeleteLibraryBlock, useLibraryBlockMetadata } from '../data/apiHooks';
|
||||
import messages from './messages';
|
||||
|
||||
@@ -34,10 +34,7 @@ interface Props {
|
||||
|
||||
const ComponentDeleter = ({ usageKey, ...props }: Props) => {
|
||||
const intl = useIntl();
|
||||
const {
|
||||
sidebarComponentInfo,
|
||||
closeLibrarySidebar,
|
||||
} = useLibraryContext();
|
||||
const { sidebarComponentInfo, closeLibrarySidebar } = useSidebarContext();
|
||||
const sidebarComponentUsageKey = sidebarComponentInfo?.id;
|
||||
|
||||
const deleteComponentMutation = useDeleteLibraryBlock();
|
||||
|
||||
@@ -4,7 +4,7 @@ import React from 'react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import EditorPage from '../../editors/EditorPage';
|
||||
import { getBlockType } from '../../generic/key-utils';
|
||||
import { useLibraryContext } from '../common/context';
|
||||
import { useLibraryContext } from '../common/context/LibraryContext';
|
||||
import { invalidateComponentData } from '../data/apiHooks';
|
||||
|
||||
export function canEditComponent(usageKey: string): boolean {
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Formik } from 'formik';
|
||||
import * as Yup from 'yup';
|
||||
import FormikControl from '../../generic/FormikControl';
|
||||
import { useLibraryContext } from '../common/context';
|
||||
import { useLibraryContext } from '../common/context/LibraryContext';
|
||||
import messages from './messages';
|
||||
import { useCreateLibraryCollection } from '../data/apiHooks';
|
||||
import { ToastContext } from '../../generic/toast-context';
|
||||
|
||||
@@ -2,8 +2,6 @@ import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { useParams, useSearchParams } from 'react-router-dom';
|
||||
import { Helmet } from 'react-helmet';
|
||||
|
||||
import { LibraryProvider } from '../common/context';
|
||||
import { getLibraryId } from '../../generic/key-utils';
|
||||
import CompareChangesWidget from '../component-comparison/CompareChangesWidget';
|
||||
import { useLibraryBlockMetadata } from '../data/apiHooks';
|
||||
import messages from '../component-comparison/messages';
|
||||
@@ -28,16 +26,15 @@ const PreviewChangesEmbed = () => {
|
||||
}
|
||||
const [queryString] = useSearchParams();
|
||||
const oldVersion = parseInt(queryString.get('old') ?? '', 10) || 'published';
|
||||
const libraryId = getLibraryId(usageKey);
|
||||
const { data: metadata } = useLibraryBlockMetadata(usageKey);
|
||||
|
||||
return (
|
||||
<LibraryProvider libraryId={libraryId}>
|
||||
<>
|
||||
{/* It's not necessary since this will usually be in an <iframe>,
|
||||
but it's good practice to set a title for any top level page */}
|
||||
<Helmet><title>{intl.formatMessage(messages.iframeTitlePrefix)} | {metadata?.displayName ?? ''} | {process.env.SITE_NAME}</title></Helmet>
|
||||
<CompareChangesWidget usageKey={usageKey} oldVersion={oldVersion} newVersion="published" />
|
||||
</LibraryProvider>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
} from '../../testUtils';
|
||||
import { mockContentLibrary } from '../data/api.mocks';
|
||||
import { getCommitLibraryChangesUrl } from '../data/api';
|
||||
import { LibraryProvider } from '../common/context';
|
||||
import { LibraryProvider } from '../common/context/LibraryContext';
|
||||
import LibraryInfo from './LibraryInfo';
|
||||
|
||||
const {
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { Button, Stack } from '@openedx/paragon';
|
||||
import { Button, Stack, useToggle } from '@openedx/paragon';
|
||||
import { FormattedDate, useIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import messages from './messages';
|
||||
import LibraryPublishStatus from './LibraryPublishStatus';
|
||||
import { useLibraryContext } from '../common/context';
|
||||
import { LibraryTeamModal } from '../library-team';
|
||||
import { useLibraryContext } from '../common/context/LibraryContext';
|
||||
|
||||
const LibraryInfo = () => {
|
||||
const intl = useIntl();
|
||||
const { libraryData, readOnly, openLibraryTeamModal } = useLibraryContext();
|
||||
const { libraryData, readOnly } = useLibraryContext();
|
||||
const [isLibraryTeamModalOpen, openLibraryTeamModal, closeLibraryTeamModal] = useToggle();
|
||||
|
||||
return (
|
||||
<Stack direction="vertical" gap={2.5}>
|
||||
@@ -56,6 +58,7 @@ const LibraryInfo = () => {
|
||||
</span>
|
||||
</Stack>
|
||||
</Stack>
|
||||
{isLibraryTeamModalOpen && <LibraryTeamModal onClose={closeLibraryTeamModal} />}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
} from '../../testUtils';
|
||||
import { mockContentLibrary } from '../data/api.mocks';
|
||||
import { getContentLibraryApiUrl } from '../data/api';
|
||||
import { LibraryProvider } from '../common/context';
|
||||
import { LibraryProvider } from '../common/context/LibraryContext';
|
||||
import LibraryInfoHeader from './LibraryInfoHeader';
|
||||
|
||||
const { libraryId: mockLibraryId, libraryIdReadOnly, libraryData } = mockContentLibrary;
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Edit } from '@openedx/paragon/icons';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { ToastContext } from '../../generic/toast-context';
|
||||
import { useLibraryContext } from '../common/context';
|
||||
import { useLibraryContext } from '../common/context/LibraryContext';
|
||||
import { useUpdateLibraryMetadata } from '../data/apiHooks';
|
||||
import messages from './messages';
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { useToggle } from '@openedx/paragon';
|
||||
import { ToastContext } from '../../generic/toast-context';
|
||||
import { useLibraryContext } from '../common/context';
|
||||
import { useLibraryContext } from '../common/context/LibraryContext';
|
||||
import { useCommitLibraryChanges, useRevertLibraryChanges } from '../data/apiHooks';
|
||||
import StatusWidget from '../generic/status-widget';
|
||||
import messages from './messages';
|
||||
|
||||
@@ -9,7 +9,7 @@ import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { AddContentContainer, AddContentHeader } from '../add-content';
|
||||
import { CollectionInfo, CollectionInfoHeader } from '../collections';
|
||||
import { SidebarBodyComponentId, useLibraryContext } from '../common/context';
|
||||
import { SidebarBodyComponentId, useSidebarContext } from '../common/context/SidebarContext';
|
||||
import { ComponentInfo, ComponentInfoHeader } from '../component-info';
|
||||
import { LibraryInfo, LibraryInfoHeader } from '../library-info';
|
||||
import messages from '../messages';
|
||||
@@ -25,10 +25,7 @@ import messages from '../messages';
|
||||
*/
|
||||
const LibrarySidebar = () => {
|
||||
const intl = useIntl();
|
||||
const {
|
||||
sidebarComponentInfo,
|
||||
closeLibrarySidebar,
|
||||
} = useLibraryContext();
|
||||
const { sidebarComponentInfo, closeLibrarySidebar } = useSidebarContext();
|
||||
|
||||
const bodyComponentMap = {
|
||||
[SidebarBodyComponentId.AddContent]: <AddContentContainer />,
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
getLibraryTeamApiUrl,
|
||||
getLibraryTeamMemberApiUrl,
|
||||
} from '../data/api';
|
||||
import { LibraryProvider } from '../common/context';
|
||||
import { LibraryProvider } from '../common/context/LibraryContext';
|
||||
import { ToastProvider } from '../../generic/toast-context';
|
||||
import LibraryTeam from './LibraryTeam';
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import { Add as IconAdd } from '@openedx/paragon/icons';
|
||||
import AlertError from '../../generic/alert-error';
|
||||
import Loading from '../../generic/Loading';
|
||||
import { ToastContext } from '../../generic/toast-context';
|
||||
import { useLibraryContext } from '../common/context';
|
||||
import { useLibraryContext } from '../common/context/LibraryContext';
|
||||
import { LibraryAccessLevel } from '../data/api';
|
||||
import {
|
||||
useContentLibrary,
|
||||
|
||||
@@ -3,23 +3,24 @@ import React from 'react';
|
||||
import { StandardModal } from '@openedx/paragon';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { useLibraryContext } from '../common/context';
|
||||
import LibraryTeam from './LibraryTeam';
|
||||
import messages from './messages';
|
||||
|
||||
export const LibraryTeamModal: React.FC<Record<never, never>> = () => {
|
||||
interface LibraryTeamModalProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const LibraryTeamModal: React.FC<LibraryTeamModalProps> = ({
|
||||
onClose,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const {
|
||||
isLibraryTeamModalOpen,
|
||||
closeLibraryTeamModal,
|
||||
} = useLibraryContext();
|
||||
|
||||
// Show Library Team modal in full screen
|
||||
return (
|
||||
<StandardModal
|
||||
isOpen
|
||||
title={intl.formatMessage(messages.modalTitle)}
|
||||
isOpen={isLibraryTeamModalOpen}
|
||||
onClose={closeLibraryTeamModal}
|
||||
onClose={onClose}
|
||||
size="lg"
|
||||
isOverflowVisible={false}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user