feat: edit components in unit page [FC-0083] (#1821)
Allows authors to edit components from unit page. It makes sure that the component preview is updated on save, allows user to double click and open editor in modal etc.
This commit is contained in:
@@ -13,6 +13,7 @@ export async function mockContentTaxonomyTagsData(contentId: string): Promise<an
|
||||
case thisMock.languageWithTagsId: return thisMock.languageWithTags;
|
||||
case thisMock.languageWithoutTagsId: return thisMock.languageWithoutTags;
|
||||
case thisMock.largeTagsId: return thisMock.largeTags;
|
||||
case thisMock.containerTagsId: return thisMock.largeTags;
|
||||
case thisMock.emptyTagsId: return thisMock.emptyTags;
|
||||
default: throw new Error(`No mock has been set up for contentId "${contentId}"`);
|
||||
}
|
||||
@@ -204,6 +205,7 @@ mockContentTaxonomyTagsData.emptyTagsId = 'block-v1:EmptyTagsOrg+STC1+2023_1+typ
|
||||
mockContentTaxonomyTagsData.emptyTags = {
|
||||
taxonomies: [],
|
||||
};
|
||||
mockContentTaxonomyTagsData.containerTagsId = 'lct:org:lib:unit:container_tags';
|
||||
mockContentTaxonomyTagsData.applyMock = () => jest.spyOn(api, 'getContentTaxonomyTagsData').mockImplementation(mockContentTaxonomyTagsData);
|
||||
|
||||
/**
|
||||
|
||||
@@ -32,6 +32,7 @@ const mockAddItemsToCollection = jest.fn();
|
||||
const mockAddComponentsToContainer = jest.fn();
|
||||
jest.spyOn(api, 'addItemsToCollection').mockImplementation(mockAddItemsToCollection);
|
||||
jest.spyOn(api, 'addComponentsToContainer').mockImplementation(mockAddComponentsToContainer);
|
||||
const unitId = 'lct:Axim:TEST:unit:test-unit-1';
|
||||
|
||||
const render = (context: 'collection' | 'unit') => baseRender(<PickLibraryContentModal isOpen onClose={onClose} />, {
|
||||
path: context === 'collection'
|
||||
@@ -40,7 +41,7 @@ const render = (context: 'collection' | 'unit') => baseRender(<PickLibraryConten
|
||||
params: {
|
||||
libraryId,
|
||||
...(context === 'collection' && { collectionId: 'collectionId' }),
|
||||
...(context === 'unit' && { unitId: 'unitId' }),
|
||||
...(context === 'unit' && { unitId }),
|
||||
},
|
||||
extraWrapper: ({ children }) => (
|
||||
<LibraryProvider
|
||||
@@ -85,7 +86,7 @@ describe('<PickLibraryContentModal />', () => {
|
||||
);
|
||||
} else {
|
||||
expect(mockAddComponentsToContainer).toHaveBeenCalledWith(
|
||||
'unitId',
|
||||
unitId,
|
||||
['lb:Axim:TEST:html:571fe018-f3ce-45c9-8f53-5dafcb422fdd'],
|
||||
);
|
||||
}
|
||||
@@ -123,7 +124,7 @@ describe('<PickLibraryContentModal />', () => {
|
||||
);
|
||||
} else {
|
||||
expect(mockAddComponentsToContainer).toHaveBeenCalledWith(
|
||||
'unitId',
|
||||
unitId,
|
||||
['lb:Axim:TEST:html:571fe018-f3ce-45c9-8f53-5dafcb422fdd'],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -482,7 +482,7 @@ export async function mockGetContainerMetadata(containerId: string): Promise<api
|
||||
mockGetContainerMetadata.containerId = 'lct:org:lib:unit:test-unit-9a207';
|
||||
mockGetContainerMetadata.containerIdError = 'lct:org:lib:unit:container_error';
|
||||
mockGetContainerMetadata.containerIdLoading = 'lct:org:lib:unit:container_loading';
|
||||
mockGetContainerMetadata.containerIdForTags = mockContentTaxonomyTagsData.largeTagsId;
|
||||
mockGetContainerMetadata.containerIdForTags = mockContentTaxonomyTagsData.containerTagsId;
|
||||
mockGetContainerMetadata.containerIdWithCollections = 'lct:org:lib:unit:container_collections';
|
||||
mockGetContainerMetadata.containerData = {
|
||||
id: 'lct:org:lib:unit:test-unit-9a2072',
|
||||
|
||||
@@ -118,7 +118,7 @@ describe('library data API', () => {
|
||||
it('should add components to unit', async () => {
|
||||
const { axiosMock } = initializeMocks();
|
||||
const componentId = 'lb:org:lib:html:1';
|
||||
const containerId = 'ltc:org:lib:unit:1';
|
||||
const containerId = 'lct:org:lib:unit:1';
|
||||
const url = api.getLibraryContainerChildrenApiUrl(containerId);
|
||||
|
||||
axiosMock.onPost(url).reply(200);
|
||||
|
||||
@@ -257,7 +257,7 @@ describe('library api hooks', () => {
|
||||
|
||||
it('should add components to container', async () => {
|
||||
const componentId = 'lb:org:lib:html:1';
|
||||
const containerId = 'ltc:org:lib:unit:1';
|
||||
const containerId = 'lct:org:lib:unit:1';
|
||||
|
||||
const url = getLibraryContainerChildrenApiUrl(containerId);
|
||||
|
||||
|
||||
@@ -101,17 +101,27 @@ export const libraryAuthoringQueryKeys = {
|
||||
'blockTypes',
|
||||
libraryId,
|
||||
],
|
||||
container: (containerId?: string) => [
|
||||
...libraryAuthoringQueryKeys.all,
|
||||
'container',
|
||||
containerId,
|
||||
],
|
||||
containerChildren: (containerId?: string) => [
|
||||
...libraryAuthoringQueryKeys.all,
|
||||
'container',
|
||||
containerId,
|
||||
'children',
|
||||
],
|
||||
container: (containerId?: string) => {
|
||||
const baseKey = containerId
|
||||
? libraryAuthoringQueryKeys.contentLibrary(getLibraryId(containerId))
|
||||
: libraryAuthoringQueryKeys.all;
|
||||
return [
|
||||
...baseKey,
|
||||
'container',
|
||||
containerId,
|
||||
];
|
||||
},
|
||||
containerChildren: (containerId?: string) => {
|
||||
const baseKey = containerId
|
||||
? libraryAuthoringQueryKeys.contentLibrary(getLibraryId(containerId))
|
||||
: libraryAuthoringQueryKeys.all;
|
||||
return [
|
||||
...baseKey,
|
||||
'container',
|
||||
containerId,
|
||||
'children',
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
export const xblockQueryKeys = {
|
||||
|
||||
@@ -26,6 +26,7 @@ import { useLibraryRoutes } from '../routes';
|
||||
import messages from './messages';
|
||||
import { useSidebarContext } from '../common/context/SidebarContext';
|
||||
import { ToastContext } from '../../generic/toast-context';
|
||||
import { canEditComponent } from '../components/ComponentEditorModal';
|
||||
|
||||
/** Components that need large min height in preview */
|
||||
const LARGE_COMPONENTS = [
|
||||
@@ -90,6 +91,7 @@ export const LibraryUnitBlocks = ({ preview }: LibraryUnitBlocksProps) => {
|
||||
componentId,
|
||||
readOnly,
|
||||
setComponentId,
|
||||
openComponentEditor,
|
||||
} = useLibraryContext();
|
||||
|
||||
const {
|
||||
@@ -131,9 +133,14 @@ export const LibraryUnitBlocks = ({ preview }: LibraryUnitBlocksProps) => {
|
||||
closeManageTagsDrawer();
|
||||
};
|
||||
|
||||
const handleComponentSelection = (block: LibraryBlockMetadata) => {
|
||||
const handleComponentSelection = (block: LibraryBlockMetadata, numberOfClicks: number) => {
|
||||
setComponentId(block.id);
|
||||
navigateTo({ componentId: block.id });
|
||||
const canEdit = canEditComponent(block.id);
|
||||
if (numberOfClicks > 1 && canEdit) {
|
||||
// Open editor on double click.
|
||||
openComponentEditor(block.id);
|
||||
}
|
||||
};
|
||||
|
||||
/* istanbul ignore next */
|
||||
@@ -166,7 +173,7 @@ export const LibraryUnitBlocks = ({ preview }: LibraryUnitBlocksProps) => {
|
||||
};
|
||||
|
||||
const renderedBlocks = orderedBlocks?.map((block) => (
|
||||
<IframeProvider key={block.id}>
|
||||
<IframeProvider key={block.id + block.modified}>
|
||||
<SortableItem
|
||||
id={block.id}
|
||||
componentStyle={null}
|
||||
@@ -179,7 +186,7 @@ export const LibraryUnitBlocks = ({ preview }: LibraryUnitBlocksProps) => {
|
||||
outline: hidePreviewFor === block.id && '2px dashed gray',
|
||||
}}
|
||||
isClickable
|
||||
onClick={() => handleComponentSelection(block)}
|
||||
onClick={(e: { detail: number; }) => handleComponentSelection(block, e.detail)}
|
||||
disabled={preview}
|
||||
>
|
||||
{hidePreviewFor !== block.id && (
|
||||
|
||||
@@ -231,4 +231,12 @@ describe('<LibraryUnitPage />', () => {
|
||||
});
|
||||
await waitFor(() => expect(mockShowToast).toHaveBeenLastCalledWith('Failed to update components order'));
|
||||
});
|
||||
|
||||
it('should show editor on double click', async () => {
|
||||
renderLibraryUnitPage();
|
||||
const component = await screen.findByText('text block 0');
|
||||
// trigger double click
|
||||
userEvent.click(component, undefined, { clickCount: 2 });
|
||||
expect(await screen.findByRole('dialog', { name: 'Editor Dialog' })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user