Skip to content

Commit 9dd5302

Browse files
committed
feat: Enable menus in component info sideber in unit page
1 parent 69f03ca commit 9dd5302

5 files changed

Lines changed: 172 additions & 28 deletions

File tree

src/content-tags-drawer/data/apiHooks.ts

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,33 @@ import { libraryAuthoringQueryKeys, libraryQueryPredicate, xblockQueryKeys } fro
2020
import { getLibraryId } from '../../generic/key-utils';
2121
import type { UpdateTagsData } from './types';
2222

23+
export const contentTagsQueryKeys = {
24+
all: ['contentTags'],
25+
taxonomyTags: (taxonomyId: number, parentTag: string | null, page: number, searchTerm: string) => [
26+
...contentTagsQueryKeys.all,
27+
'taxonomyTags',
28+
taxonomyId,
29+
parentTag,
30+
page,
31+
searchTerm,
32+
],
33+
contentTaxonomyTags: (contentId: string) => [
34+
...contentTagsQueryKeys.all,
35+
'contentTaxonomyTags',
36+
contentId,
37+
],
38+
contentData: (contentId?: string) => [
39+
...contentTagsQueryKeys.all,
40+
'contentData',
41+
contentId,
42+
],
43+
contentTagsCount: (contentPattern: string) => [
44+
...contentTagsQueryKeys.all,
45+
'contentTagsCount',
46+
contentPattern,
47+
],
48+
};
49+
2350
/**
2451
* Builds the query to get the taxonomy tags
2552
*/
@@ -43,7 +70,7 @@ export const useTaxonomyTagsData = (
4370
const queries: { queryKey: any[]; queryFn: typeof queryFn; staleTime: number }[] = [];
4471
for (let page = 1; page <= numPages; page++) {
4572
queries.push(
46-
{ queryKey: ['taxonomyTags', taxonomyId, parentTag, page, searchTerm], queryFn, staleTime: Infinity },
73+
{ queryKey: contentTagsQueryKeys.taxonomyTags(taxonomyId, parentTag, page, searchTerm), queryFn, staleTime: Infinity },
4774
);
4875
}
4976

@@ -74,7 +101,7 @@ export const useTaxonomyTagsData = (
74101

75102
// Store the pre-loaded descendants into the query cache:
76103
preLoadedData.forEach((tags, parentValue) => {
77-
const queryKey = ['taxonomyTags', taxonomyId, parentValue, 1, searchTerm];
104+
const queryKey = contentTagsQueryKeys.taxonomyTags(taxonomyId, parentValue, 1, searchTerm);
78105
const cachedData: TagListData = {
79106
next: '',
80107
previous: '',
@@ -106,7 +133,7 @@ export const useTaxonomyTagsData = (
106133
*/
107134
export const useContentTaxonomyTagsData = (contentId: string) => (
108135
useQuery({
109-
queryKey: ['contentTaxonomyTags', contentId],
136+
queryKey: contentTagsQueryKeys.contentTaxonomyTags(contentId),
110137
queryFn: () => getContentTaxonomyTagsData(contentId),
111138
})
112139
);
@@ -118,7 +145,7 @@ export const useContentTaxonomyTagsData = (contentId: string) => (
118145
*/
119146
export const useContentData = (contentId?: string, enabled: boolean = true) => (
120147
useQuery({
121-
queryKey: ['contentData', contentId],
148+
queryKey: contentTagsQueryKeys.contentData(contentId),
122149
queryFn: (enabled && contentId) ? () => getContentData(contentId) : skipToken,
123150
})
124151
);
@@ -137,15 +164,15 @@ export const useContentTaxonomyTagsUpdater = (contentId: string) => {
137164
updateContentTaxonomyTags(contentId, tagsData)
138165
),
139166
onSettled: () => {
140-
queryClient.invalidateQueries({ queryKey: ['contentTaxonomyTags', contentId] });
167+
queryClient.invalidateQueries({ queryKey: contentTagsQueryKeys.contentTaxonomyTags(contentId) });
141168
/// Invalidate query with pattern on course outline
142169
let contentPattern;
143170
if (contentId.includes('course-v1')) {
144171
contentPattern = contentId;
145172
} else {
146173
contentPattern = contentId.replace(/\+type@.*$/, '*');
147174
}
148-
queryClient.invalidateQueries({ queryKey: ['contentTagsCount', contentPattern] });
175+
queryClient.invalidateQueries({ queryKey: contentTagsQueryKeys.contentTagsCount(contentPattern) });
149176
if (contentId.startsWith('lb:') || contentId.startsWith('lib-collection:') || contentId.startsWith('lct:')) {
150177
// Obtain library id from contentId
151178
const libraryId = getLibraryId(contentId);

src/course-unit/unit-sidebar/unit-info/ComponentInfoSidebar.tsx

Lines changed: 115 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,25 @@
1-
import { useSelector } from 'react-redux';
1+
import { useEffect } from 'react';
2+
import { useDispatch, useSelector } from 'react-redux';
23
import { useIntl } from '@edx/frontend-platform/i18n';
34
import { useNavigate } from 'react-router-dom';
45
import { Tag } from '@openedx/paragon/icons';
56
import { useQueryClient } from '@tanstack/react-query';
7+
import { useToggle } from '@openedx/paragon';
68

79
import { SidebarContent, SidebarSection, SidebarTitle } from '@src/generic/sidebar';
810
import { ContentTagsSnippet } from '@src/content-tags-drawer';
9-
import { useContentData } from '@src/content-tags-drawer/data/apiHooks';
10-
import type { XBlockData } from '@src/content-tags-drawer/data/types';
1111
import { getItemIcon } from '@src/generic/block-type-utils';
1212
import { useIframe } from '@src/generic/hooks/context/hooks';
1313
import { messageTypes } from '@src/course-unit/constants';
1414
import { LibraryReferenceCard } from '@src/generic/library-reference-card/LibraryReferenceCard';
15-
import { getCourseUnitData } from '@src/course-unit/data/selectors';
15+
import { getCourseUnitData, getMovedXBlockParams } from '@src/course-unit/data/selectors';
1616
import { useCourseAuthoringContext } from '@src/CourseAuthoringContext';
17-
import { courseOutlineQueryKeys } from '@src/course-outline/data/apiHooks';
17+
import { courseOutlineQueryKeys, useCourseItemData } from '@src/course-outline/data/apiHooks';
18+
import { getLibraryId } from '@src/generic/key-utils';
19+
import { useUnlinkDownstream, UnlinkModal } from '@src/generic/unlink-modal';
20+
import { useClipboard } from '@src/generic/clipboard';
21+
import DeleteModal from '@src/generic/delete-modal/DeleteModal';
22+
import { deleteUnitItemQuery, duplicateUnitItemQuery, fetchCourseVerticalChildrenData } from '@src/course-unit/data/thunk';
1823

1924
import { useUnitSidebarContext } from '../UnitSidebarContext';
2025
import messages from './messages';
@@ -26,19 +31,31 @@ export const ComponentInfoSidebar = () => {
2631
const intl = useIntl();
2732
const queryClient = useQueryClient();
2833
const navigate = useNavigate();
34+
const dispatch = useDispatch();
2935
const { sendMessageToIframe } = useIframe();
36+
const { copyToClipboard } = useClipboard();
3037
const unitData = useSelector(getCourseUnitData);
3138
const { courseId } = useCourseAuthoringContext();
3239
const sectionId = unitData?.ancestorInfo?.ancestors?.find(
3340
(ancestor) => ancestor.category === 'chapter',
3441
)?.id;
42+
const [isUnlinkModalOpen, openUnlinkModal, closeUnlinkModal] = useToggle(false);
43+
const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useToggle(false);
3544

3645
const {
3746
selectedComponentId,
3847
setCurrentPageKey,
3948
} = useUnitSidebarContext();
49+
const { mutateAsync: unlinkDownstream } = useUnlinkDownstream();
4050

41-
const { data: contentData } = useContentData(selectedComponentId) as { data: XBlockData | undefined };
51+
const { data: componentItemData } = useCourseItemData(selectedComponentId ?? undefined);
52+
const movedXBlockParams = useSelector(getMovedXBlockParams);
53+
54+
useEffect(() => {
55+
if (movedXBlockParams.isSuccess && movedXBlockParams.sourceLocator === selectedComponentId) {
56+
setCurrentPageKey('info', null);
57+
}
58+
}, [movedXBlockParams]);
4259

4360
// istanbul ignore next
4461
const handleBack = () => {
@@ -57,21 +74,92 @@ export const ComponentInfoSidebar = () => {
5774
});
5875
};
5976

77+
const handleDuplicate = () => {
78+
if (selectedComponentId && unitData?.id) {
79+
dispatch(duplicateUnitItemQuery(
80+
unitData.id,
81+
selectedComponentId,
82+
(courseKey: string, locator: string) => sendMessageToIframe(
83+
messageTypes.completeXBlockDuplicating,
84+
{ courseKey, locator },
85+
),
86+
));
87+
}
88+
};
89+
90+
const handleDeleteSubmit = async () => {
91+
if (selectedComponentId && unitData?.id) {
92+
await dispatch(deleteUnitItemQuery(unitData.id, selectedComponentId, sendMessageToIframe));
93+
closeDeleteModal();
94+
setCurrentPageKey('info', null);
95+
if (unitData?.id) {
96+
dispatch(fetchCourseVerticalChildrenData(unitData.id, false));
97+
}
98+
}
99+
};
100+
101+
const handleUnlinkSubmit = async () => {
102+
if (selectedComponentId) {
103+
await unlinkDownstream({
104+
downstreamBlockId: selectedComponentId,
105+
}, {
106+
onSuccess: () => {
107+
closeUnlinkModal();
108+
queryClient.invalidateQueries({
109+
queryKey: courseOutlineQueryKeys.courseItemId(selectedComponentId),
110+
});
111+
if (unitData?.id) {
112+
dispatch(fetchCourseVerticalChildrenData(unitData.id, false));
113+
}
114+
},
115+
});
116+
}
117+
};
118+
119+
const handleMove = () => {
120+
if (!selectedComponentId || !unitData?.id) {
121+
return;
122+
}
123+
window.dispatchEvent(new MessageEvent('message', {
124+
data: {
125+
type: messageTypes.showMoveXBlockModal,
126+
payload: {
127+
sourceXBlockInfo: {
128+
id: selectedComponentId,
129+
displayName: componentItemData?.displayName ?? '',
130+
category: componentItemData?.category ?? '',
131+
},
132+
sourceParentXBlockInfo: {
133+
id: unitData.id,
134+
displayName: unitData.displayName ?? '',
135+
category: 'vertical',
136+
},
137+
},
138+
},
139+
}));
140+
};
141+
60142
return (
61143
<>
62144
<SidebarTitle
63-
title={contentData?.displayName || ''}
64-
icon={getItemIcon(contentData?.category || '')}
145+
title={componentItemData?.displayName || ''}
146+
icon={getItemIcon(componentItemData?.category || '')}
65147
onBackBtnClick={handleBack}
66148
menuProps={{
67149
itemId: selectedComponentId || '',
68150
index: 0,
69-
onClickDuplicate: () => {},
70-
onClickUnlink: () => {},
71-
onClickDelete: () => {},
72-
onClickViewLibrary: () => {},
73-
onClickCopy: () => {},
74-
onClickMove: () => {},
151+
onClickDuplicate: handleDuplicate,
152+
onClickUnlink: openUnlinkModal,
153+
onClickDelete: openDeleteModal,
154+
onClickViewLibrary: () => {
155+
const upstreamRef = componentItemData?.upstreamInfo?.upstreamRef;
156+
if (upstreamRef) {
157+
const libId = getLibraryId(upstreamRef);
158+
navigate(`/library/${libId}/components/${upstreamRef}`);
159+
}
160+
},
161+
onClickCopy: () => copyToClipboard(selectedComponentId ?? ''),
162+
onClickMove: handleMove,
75163
}}
76164
/>
77165
<LibraryReferenceCard
@@ -88,6 +176,19 @@ export const ComponentInfoSidebar = () => {
88176
<ContentTagsSnippet contentId={selectedComponentId || ''} />
89177
</SidebarSection>
90178
</SidebarContent>
179+
<UnlinkModal
180+
isOpen={isUnlinkModalOpen}
181+
close={closeUnlinkModal}
182+
onUnlinkSubmit={handleUnlinkSubmit}
183+
displayName={componentItemData?.displayName}
184+
category="vertical"
185+
/>
186+
<DeleteModal
187+
isOpen={isDeleteModalOpen}
188+
close={closeDeleteModal}
189+
category={componentItemData?.category}
190+
onDeleteSubmit={handleDeleteSubmit}
191+
/>
91192
</>
92193
);
93194
};

src/course-unit/unit-sidebar/unit-info/UnitInfoSidebar.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,14 @@ import { AccessEditComponent, DiscussionEditComponent } from '@src/generic/confi
1616
import { Form, Formik } from 'formik';
1717
import { getCourseUnitData, getCourseVerticalChildren } from '@src/course-unit/data/selectors';
1818
import { messageTypes, PUBLISH_TYPES, UNIT_VISIBILITY_STATES } from '@src/course-unit/constants';
19-
import { editCourseUnitVisibilityAndData, fetchCourseSectionVerticalData, fetchCourseVerticalChildrenData } from '@src/course-unit/data/thunk';
19+
import { editCourseUnitVisibilityAndData, fetchCourseSectionVerticalData } from '@src/course-unit/data/thunk';
2020
import PublishControls from './PublishControls';
2121
import { useUnitSidebarContext } from '../UnitSidebarContext';
2222
import messages from './messages';
2323
import { getLibraryId } from '@src/generic/key-utils';
2424
import { useClipboard } from '@src/generic/clipboard';
2525
import { ToastContext } from '@src/generic/toast-context';
26-
import { UnlinkModal } from '@src/generic/unlink-modal';
27-
import { useUnlinkDownstream } from '@src/generic/unlink-modal/data/apiHooks';
26+
import { UnlinkModal, useUnlinkDownstream } from '@src/generic/unlink-modal';
2827
import { useCourseAuthoringContext } from '@src/CourseAuthoringContext';
2928
import { useQueryClient } from '@tanstack/react-query';
3029
import { courseOutlineQueryKeys, useDeleteCourseItem } from '@src/course-outline/data/apiHooks';
@@ -246,7 +245,6 @@ export const UnitInfoSidebar = () => {
246245
closeUnlinkModal();
247246
queryClient.invalidateQueries({ queryKey: courseOutlineQueryKeys.courseItemId(currentItemData.id) });
248247
dispatch(fetchCourseSectionVerticalData(currentItemData.id, sequenceId));
249-
dispatch(fetchCourseVerticalChildrenData(currentItemData.id, false));
250248
},
251249
});
252250
};

src/course-unit/xblock-container-iframe/index.tsx

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useQueryClient } from '@tanstack/react-query';
12
import { getConfig } from '@edx/frontend-platform';
23
import {
34
FC, useEffect, useState, useMemo, useCallback,
@@ -43,6 +44,8 @@ import {
4344
import { formatAccessManagedXBlockData, getIframeUrl, getLegacyEditModalUrl } from './utils';
4445
import { useUnitSidebarContext } from '../unit-sidebar/UnitSidebarContext';
4546
import { isUnitPageNewDesignEnabled } from '../utils';
47+
import { courseOutlineQueryKeys } from '@src/course-outline/data/apiHooks';
48+
import { contentTagsQueryKeys } from '@src/content-tags-drawer/data/apiHooks';
4649

4750
const XBlockContainerIframe: FC<XBlockContainerIframeProps> = ({
4851
courseId,
@@ -54,6 +57,7 @@ const XBlockContainerIframe: FC<XBlockContainerIframeProps> = ({
5457
readonly,
5558
}) => {
5659
const intl = useIntl();
60+
const queryClient = useQueryClient();
5761
const dispatch = useDispatch();
5862
const {
5963
setCurrentPageKey,
@@ -93,11 +97,21 @@ const XBlockContainerIframe: FC<XBlockContainerIframeProps> = ({
9397
setIframeRef(iframeRef);
9498
}, [setIframeRef]);
9599

100+
const refreshComponent = (id: string) => {
101+
queryClient.invalidateQueries({
102+
queryKey: courseOutlineQueryKeys.courseItemId(id),
103+
});
104+
queryClient.invalidateQueries({
105+
queryKey: contentTagsQueryKeys.contentData(id),
106+
});
107+
};
108+
96109
const onXBlockSave = useCallback(/* istanbul ignore next */ () => {
97110
closeXBlockEditorModal();
98111
closeVideoSelectorModal();
99112
sendMessageToIframe(messageTypes.refreshXBlock, null);
100-
}, [closeXBlockEditorModal, closeVideoSelectorModal, sendMessageToIframe]);
113+
refreshComponent(newBlockId);
114+
}, [closeXBlockEditorModal, closeVideoSelectorModal, sendMessageToIframe, newBlockId]);
101115

102116
const handleEditXBlock = useCallback((type: string, id: string) => {
103117
setBlockType(type);
@@ -143,10 +157,11 @@ const XBlockContainerIframe: FC<XBlockContainerIframeProps> = ({
143157
}
144158
};
145159

146-
const onUnlinkSubmit = () => {
160+
const onUnlinkSubmit = async () => {
147161
if (unlinkXBlockId) {
148-
unitXBlockActions.handleUnlink(unlinkXBlockId);
162+
await unitXBlockActions.handleUnlink(unlinkXBlockId);
149163
closeUnlinkModal();
164+
refreshComponent(unlinkXBlockId);
150165
}
151166
};
152167

@@ -181,6 +196,9 @@ const XBlockContainerIframe: FC<XBlockContainerIframeProps> = ({
181196
const handleSaveEditedXBlockData = () => {
182197
sendMessageToIframe(messageTypes.completeXBlockEditing, { locator: configureXBlockId });
183198
dispatch(updateCourseUnitSidebar(blockId));
199+
if (configureXBlockId) {
200+
refreshComponent(configureXBlockId);
201+
}
184202
if (!isUnitVerticalType) {
185203
dispatch(fetchCourseSectionVerticalData(blockId));
186204
}

src/course-unit/xblock-container-iframe/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export interface XBlockContainerIframeProps {
3434
unitXBlockActions: {
3535
handleDelete: (XBlockId: string | null) => Promise<void> | void;
3636
handleDuplicate: (XBlockId: string | null) => void;
37-
handleUnlink: (XBlockId: string | null) => void;
37+
handleUnlink: (XBlockId: string | null) => Promise<void> | void;
3838
};
3939
courseVerticalChildren: Array<XBlockTypes>;
4040
handleConfigureSubmit: (variables: ConfigureUnitData & { closeModalFn?: () => void }) => void;

0 commit comments

Comments
 (0)