Adds new routes and URL parameters to use when viewing and performing searches on library components. These changes allow these pages to be bookmarked or shared by copy/pasting the browser's current URL. No changes were made to the UI. Use cases covered: * As an author working with content libraries, I want to easily share any component in a library with other people on my team, by copying the URL from my browser and sending it to them. * As an author working with content libraries, I want to easily share any search results with other people on my team, by copying the URL from my browser and sending it to them. * As an author working with content libraries, I want to bookmark a search in my browser and return to it at any time, with the same filters and keywords applied. * As an author of a content library with public read access, I want to easily share any component in a library with any authors on the same Open edX instance, by copying the URL from my browser and sending it to them. * As an author of a content library, I want to easily share a library's "Manage Team" page with other people on my team by copying the URL from my browser and sending it to them. * As an author working with content libraries, I want to easily share any selected sidebar tab with other people on my team, by copying the URL from my browser and sending it to them.
125 lines
4.5 KiB
TypeScript
125 lines
4.5 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { useLocation } from 'react-router-dom';
|
|
import { Alert, Stepper } from '@openedx/paragon';
|
|
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
|
|
|
import {
|
|
type ComponentSelectedEvent,
|
|
type ComponentSelectionChangedEvent,
|
|
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';
|
|
import messages from './messages';
|
|
|
|
interface LibraryComponentPickerProps {
|
|
returnToLibrarySelection: () => void;
|
|
}
|
|
|
|
const InnerComponentPicker: React.FC<LibraryComponentPickerProps> = ({ returnToLibrarySelection }) => {
|
|
const { collectionId } = useLibraryContext();
|
|
|
|
if (collectionId) {
|
|
return <LibraryCollectionPage />;
|
|
}
|
|
return <LibraryAuthoringPage returnToLibrarySelection={returnToLibrarySelection} />;
|
|
};
|
|
|
|
/** Default handler in single-select mode. Used by the legacy UI for adding a single selected component to a course. */
|
|
const defaultComponentSelectedCallback: ComponentSelectedEvent = ({ usageKey, blockType }) => {
|
|
window.parent.postMessage({ usageKey, type: 'pickerComponentSelected', category: blockType }, '*');
|
|
};
|
|
|
|
/** Default handler in multi-select mode. Used by the legacy UI for adding components to a problem bank. */
|
|
const defaultSelectionChangedCallback: ComponentSelectionChangedEvent = (selections) => {
|
|
window.parent.postMessage({ type: 'pickerSelectionChanged', selections }, '*');
|
|
};
|
|
|
|
type ComponentPickerProps = { libraryId?: string, showOnlyPublished?: boolean } & (
|
|
{
|
|
componentPickerMode?: 'single',
|
|
onComponentSelected?: ComponentSelectedEvent,
|
|
onChangeComponentSelection?: never,
|
|
} | {
|
|
componentPickerMode: 'multiple'
|
|
onComponentSelected?: never,
|
|
onChangeComponentSelection?: ComponentSelectionChangedEvent,
|
|
}
|
|
);
|
|
|
|
export const ComponentPicker: React.FC<ComponentPickerProps> = ({
|
|
/** Restrict the component picker to a specific library */
|
|
libraryId,
|
|
showOnlyPublished,
|
|
componentPickerMode = 'single',
|
|
/** This default callback is used to send the selected component back to the parent window,
|
|
* when the component picker is used in an iframe.
|
|
*/
|
|
onComponentSelected = defaultComponentSelectedCallback,
|
|
onChangeComponentSelection = defaultSelectionChangedCallback,
|
|
}) => {
|
|
const [currentStep, setCurrentStep] = useState(!libraryId ? 'select-library' : 'pick-components');
|
|
const [selectedLibrary, setSelectedLibrary] = useState(libraryId || '');
|
|
|
|
const location = useLocation();
|
|
|
|
const queryParams = new URLSearchParams(location.search);
|
|
const variant = queryParams.get('variant') || 'draft';
|
|
const calcShowOnlyPublished = variant === 'published' || showOnlyPublished;
|
|
|
|
const handleLibrarySelection = (library: string) => {
|
|
setCurrentStep('pick-components');
|
|
setSelectedLibrary(library);
|
|
};
|
|
|
|
const returnToLibrarySelection = () => {
|
|
setCurrentStep('select-library');
|
|
setSelectedLibrary('');
|
|
};
|
|
|
|
const restrictToLibrary = !!libraryId;
|
|
|
|
const componentPickerProviderProps = componentPickerMode === 'single' ? {
|
|
componentPickerMode,
|
|
onComponentSelected,
|
|
restrictToLibrary,
|
|
} : {
|
|
componentPickerMode,
|
|
onChangeComponentSelection,
|
|
restrictToLibrary,
|
|
};
|
|
|
|
return (
|
|
<Stepper
|
|
activeKey={currentStep}
|
|
>
|
|
<Stepper.Step eventKey="select-library" title="Select a library">
|
|
<SelectLibrary selectedLibrary={selectedLibrary} setSelectedLibrary={handleLibrarySelection} />
|
|
</Stepper.Step>
|
|
|
|
<Stepper.Step eventKey="pick-components" title="Pick some components">
|
|
<ComponentPickerProvider {...componentPickerProviderProps}>
|
|
<LibraryProvider
|
|
libraryId={selectedLibrary}
|
|
showOnlyPublished={calcShowOnlyPublished}
|
|
skipUrlUpdate
|
|
>
|
|
<SidebarProvider>
|
|
{ calcShowOnlyPublished
|
|
&& (
|
|
<Alert variant="info" className="m-2">
|
|
<FormattedMessage {...messages.pickerInfoBanner} />
|
|
</Alert>
|
|
)}
|
|
<InnerComponentPicker returnToLibrarySelection={returnToLibrarySelection} />
|
|
</SidebarProvider>
|
|
</LibraryProvider>
|
|
</ComponentPickerProvider>
|
|
</Stepper.Step>
|
|
</Stepper>
|
|
);
|
|
};
|