refactor: split up library context (#1539)

Split the library context into smaller contexts:

* LibraryContext
* ComponentPickerContext
* SidebarContext
This commit is contained in:
Rômulo Penido
2024-12-12 15:14:45 -03:00
committed by GitHub
parent c7e2bf9934
commit 69bbeda816
54 changed files with 764 additions and 656 deletions

View File

@@ -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,

View File

@@ -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);

View File

@@ -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>
),
});

View File

@@ -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) {

View File

@@ -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>
);
};

View File

@@ -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';

View File

@@ -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';

View File

@@ -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 {

View File

@@ -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';

View File

@@ -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>
),
});

View File

@@ -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

View File

@@ -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;

View File

@@ -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>
),
});

View File

@@ -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

View File

@@ -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

View File

@@ -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,

View File

@@ -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;
}

View 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;
}

View 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;
}

View 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;
}

View File

@@ -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

View File

@@ -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>
),
},

View File

@@ -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

View File

@@ -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>
),
});

View File

@@ -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;

View File

@@ -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>
),
});

View File

@@ -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;

View File

@@ -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>
),
});

View File

@@ -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

View File

@@ -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>
),
});

View File

@@ -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);

View File

@@ -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>
),
});

View File

@@ -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

View File

@@ -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>
),
});

View File

@@ -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);

View File

@@ -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>
);

View File

@@ -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 () => {

View File

@@ -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,

View File

@@ -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 />', () => {

View File

@@ -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,

View File

@@ -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 />', () => {

View File

@@ -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();

View File

@@ -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 {

View File

@@ -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';

View File

@@ -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>
</>
);
};

View File

@@ -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 {

View File

@@ -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>
);
};

View File

@@ -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;

View File

@@ -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';

View File

@@ -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';

View File

@@ -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 />,

View File

@@ -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';

View File

@@ -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,

View File

@@ -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}
>