Skip to content

Commit a43027b

Browse files
authored
feat: library unit page skeleton [FC-0083] (#1779)
* View a unit page, which has its own URL * Components appear within a unit as full previews. Their top bar shows type icon and title on the left, and draft status (if any), tag count, overflow menu, and drag handle on the right. * Components have an overflow menu within a unit * Components can be selected within a unit * When components are selected, the standard component sidebar appears. The preview tab is hidden, since component previews are visible in the main content area. * Components within a unit full-page view have hover and selected states * Unit sidebar preview. * Frontend implementation Drag-n-drop components to reorder them in unit.
1 parent 01365d0 commit a43027b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+1204
-517
lines changed

src/CourseAuthoringRoutes.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import ScheduleAndDetails from './schedule-and-details';
1717
import { GradingSettings } from './grading-settings';
1818
import CourseTeam from './course-team/CourseTeam';
1919
import { CourseUpdates } from './course-updates';
20-
import { CourseUnit, IframeProvider } from './course-unit';
20+
import { CourseUnit } from './course-unit';
2121
import { Certificates } from './certificates';
2222
import CourseExportPage from './export-page/CourseExportPage';
2323
import CourseOptimizerPage from './optimizer-page/CourseOptimizerPage';
@@ -26,6 +26,7 @@ import { DECODED_ROUTES } from './constants';
2626
import CourseChecklist from './course-checklist';
2727
import GroupConfigurations from './group-configurations';
2828
import { CourseLibraries } from './course-libraries';
29+
import { IframeProvider } from './generic/hooks/context/iFrameContext';
2930

3031
/**
3132
* As of this writing, these routes are mounted at a path prefixed with the following:

src/constants.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,17 @@ export const REGEX_RULES = {
9292
export const IFRAME_FEATURE_POLICY = (
9393
'microphone *; camera *; midi *; geolocation *; encrypted-media *; clipboard-write *'
9494
);
95+
96+
export const iframeStateKeys = {
97+
iframeHeight: 'iframeHeight',
98+
hasLoaded: 'hasLoaded',
99+
showError: 'showError',
100+
windowTopOffset: 'windowTopOffset',
101+
};
102+
103+
export const iframeMessageTypes = {
104+
modal: 'plugin.modal',
105+
resize: 'plugin.resize',
106+
videoFullScreen: 'plugin.videoFullScreen',
107+
xblockEvent: 'xblock-event',
108+
};

src/course-unit/CourseUnit.test.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ import configureModalMessages from '../generic/configure-modal/messages';
5959
import { getContentTaxonomyTagsApiUrl, getContentTaxonomyTagsCountApiUrl } from '../content-tags-drawer/data/api';
6060
import addComponentMessages from './add-component/messages';
6161
import { messageTypes, PUBLISH_TYPES, UNIT_VISIBILITY_STATES } from './constants';
62-
import { IframeProvider } from './context/iFrameContext';
62+
import { IframeProvider } from '../generic/hooks/context/iFrameContext';
6363
import moveModalMessages from './move-modal/messages';
6464
import xblockContainerIframeMessages from './xblock-container-iframe/messages';
6565
import headerNavigationsMessages from './header-navigations/messages';

src/course-unit/add-component/AddComponent.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import AddComponentButton from './add-component-btn';
1414
import messages from './messages';
1515
import { ComponentPicker } from '../../library-authoring/component-picker';
1616
import { messageTypes } from '../constants';
17-
import { useIframe } from '../context/hooks';
17+
import { useIframe } from '../../generic/hooks/context/hooks';
1818
import { useEventListener } from '../../generic/hooks';
1919

2020
const AddComponent = ({

src/course-unit/add-component/AddComponent.test.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { courseSectionVerticalMock } from '../__mocks__';
1818
import { COMPONENT_TYPES } from '../../generic/block-type-utils/constants';
1919
import AddComponent from './AddComponent';
2020
import messages from './messages';
21-
import { IframeProvider } from '../context/iFrameContext';
21+
import { IframeProvider } from '../../generic/hooks/context/iFrameContext';
2222
import { messageTypes } from '../constants';
2323

2424
let store;
@@ -52,7 +52,7 @@ jest.mock('../../library-authoring/component-picker', () => ({
5252
}));
5353

5454
const mockSendMessageToIframe = jest.fn();
55-
jest.mock('../context/hooks', () => ({
55+
jest.mock('../../generic/hooks/context/hooks', () => ({
5656
useIframe: () => ({
5757
sendMessageToIframe: mockSendMessageToIframe,
5858
}),

src/course-unit/constants.js

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,7 @@ export const getXBlockSupportMessages = (intl) => ({
3939
},
4040
});
4141

42-
export const stateKeys = {
43-
iframeHeight: 'iframeHeight',
44-
hasLoaded: 'hasLoaded',
45-
showError: 'showError',
46-
windowTopOffset: 'windowTopOffset',
47-
};
48-
4942
export const messageTypes = {
50-
modal: 'plugin.modal',
51-
resize: 'plugin.resize',
52-
videoFullScreen: 'plugin.videoFullScreen',
5343
refreshXBlock: 'refreshXBlock',
5444
showMoveXBlockModal: 'showMoveXBlockModal',
5545
completeXBlockMoving: 'completeXBlockMoving',

src/course-unit/hooks.jsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { camelCaseObject } from '@edx/frontend-platform/utils';
99
import { RequestStatus } from '../data/constants';
1010
import { useClipboard } from '../generic/clipboard';
1111
import { useEventListener } from '../generic/hooks';
12-
import { COURSE_BLOCK_NAMES } from '../constants';
12+
import { COURSE_BLOCK_NAMES, iframeMessageTypes } from '../constants';
1313
import { messageTypes, PUBLISH_TYPES } from './constants';
1414
import {
1515
createNewCourseXBlock,
@@ -41,7 +41,7 @@ import {
4141
updateMovedXBlockParams,
4242
updateQueryPendingStatus,
4343
} from './data/slice';
44-
import { useIframe } from './context/hooks';
44+
import { useIframe } from '../generic/hooks/context/hooks';
4545

4646
export const useCourseUnit = ({ courseId, blockId }) => {
4747
const dispatch = useDispatch();
@@ -313,7 +313,7 @@ export const useScrollToLastPosition = (storageKey = 'createXBlockLastYPosition'
313313
}, [storageKey]);
314314

315315
const handleMessage = useCallback((event) => {
316-
if (event.data?.type === messageTypes.resize) {
316+
if (event.data?.type === iframeMessageTypes.resize) {
317317
if (timeoutRef.current) {
318318
clearTimeout(timeoutRef.current);
319319
}

src/course-unit/hooks.test.jsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react';
22
import { act, renderHook } from '@testing-library/react';
33
import { useScrollToLastPosition, useLayoutGrid } from './hooks';
4-
import { messageTypes } from './constants';
4+
import { iframeMessageTypes } from '../constants';
55

66
jest.useFakeTimers();
77

@@ -108,7 +108,7 @@ describe('useScrollToLastPosition', () => {
108108
const { unmount } = renderHook(() => useScrollToLastPosition(storageKey));
109109

110110
act(() => {
111-
window.dispatchEvent(new MessageEvent('message', { data: { type: messageTypes.resize } }));
111+
window.dispatchEvent(new MessageEvent('message', { data: { type: iframeMessageTypes.resize } }));
112112
jest.advanceTimersByTime(1000);
113113
});
114114

@@ -136,8 +136,8 @@ describe('useScrollToLastPosition', () => {
136136
renderHook(() => useScrollToLastPosition(storageKey));
137137

138138
act(() => {
139-
window.dispatchEvent(new MessageEvent('message', { data: { type: messageTypes.resize } }));
140-
window.dispatchEvent(new MessageEvent('message', { data: { type: messageTypes.resize } }));
139+
window.dispatchEvent(new MessageEvent('message', { data: { type: iframeMessageTypes.resize } }));
140+
window.dispatchEvent(new MessageEvent('message', { data: { type: iframeMessageTypes.resize } }));
141141
});
142142

143143
expect(clearTimeoutSpy).toHaveBeenCalled();
@@ -150,9 +150,9 @@ describe('useScrollToLastPosition', () => {
150150
renderHook(() => useScrollToLastPosition(storageKey));
151151

152152
act(() => {
153-
window.dispatchEvent(new MessageEvent('message', { data: { type: messageTypes.resize } }));
153+
window.dispatchEvent(new MessageEvent('message', { data: { type: iframeMessageTypes.resize } }));
154154
jest.advanceTimersByTime(500);
155-
window.dispatchEvent(new MessageEvent('message', { data: { type: messageTypes.resize } }));
155+
window.dispatchEvent(new MessageEvent('message', { data: { type: iframeMessageTypes.resize } }));
156156
});
157157

158158
expect(window.scrollTo).not.toHaveBeenCalled();
@@ -164,7 +164,7 @@ describe('useScrollToLastPosition', () => {
164164
renderHook(() => useScrollToLastPosition(storageKey));
165165

166166
act(() => {
167-
window.dispatchEvent(new MessageEvent('message', { data: { type: messageTypes.resize } }));
167+
window.dispatchEvent(new MessageEvent('message', { data: { type: iframeMessageTypes.resize } }));
168168
jest.advanceTimersByTime(1000);
169169
});
170170

src/course-unit/index.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
export { default as CourseUnit } from './CourseUnit';
2-
export { IframeProvider } from './context/iFrameContext';

src/course-unit/move-modal/hooks.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { RequestStatus } from '../../data/constants';
1111
import { useEventListener } from '../../generic/hooks';
1212
import { getCourseOutlineInfo, getCourseOutlineInfoLoadingStatus } from '../data/selectors';
1313
import { getCourseOutlineInfoQuery, patchUnitItemQuery } from '../data/thunk';
14-
import { useIframe } from '../context/hooks';
14+
import { useIframe } from '../../generic/hooks/context/hooks';
1515
import { messageTypes } from '../constants';
1616
import { CATEGORIES, MOVE_DIRECTIONS } from './constants';
1717
import {

src/course-unit/move-modal/moveModal.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { getCourseOutlineInfoUrl } from '../data/api';
1111
import { courseOutlineInfoMock } from '../__mocks__';
1212
import { executeThunk } from '../../utils';
1313
import { getCourseOutlineInfoQuery } from '../data/thunk';
14-
import { IframeProvider } from '../context/iFrameContext';
14+
import { IframeProvider } from '../../generic/hooks/context/iFrameContext';
1515
import { IXBlock } from './interfaces';
1616
import MoveModal from './index';
1717
import messages from './messages';

src/course-unit/preview-changes/index.test.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010

1111
import IframePreviewLibraryXBlockChanges, { LibraryChangesMessageData } from '.';
1212
import { messageTypes } from '../constants';
13-
import { IframeProvider } from '../context/iFrameContext';
1413
import { libraryBlockChangesUrl } from '../data/api';
1514
import { ToastActionData } from '../../generic/toast-context';
1615
import { getLibraryBlockMetadataUrl } from '../../library-authoring/data/api';
@@ -25,15 +24,15 @@ const defaultEventData: LibraryChangesMessageData = {
2524
};
2625

2726
const mockSendMessageToIframe = jest.fn();
28-
jest.mock('../context/hooks', () => ({
27+
jest.mock('../../generic/hooks/context/hooks', () => ({
2928
useIframe: () => ({
29+
iframeRef: { current: { contentWindow: {} as HTMLIFrameElement } },
30+
setIframeRef: () => {},
3031
sendMessageToIframe: mockSendMessageToIframe,
3132
}),
3233
}));
3334
const render = (eventData?: LibraryChangesMessageData) => {
34-
baseRender(<IframePreviewLibraryXBlockChanges />, {
35-
extraWrapper: ({ children }) => <IframeProvider>{ children }</IframeProvider>,
36-
});
35+
baseRender(<IframePreviewLibraryXBlockChanges />);
3736
const message = {
3837
data: {
3938
type: messageTypes.showXBlockLibraryChangesPreview,

src/course-unit/preview-changes/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { useEventListener } from '../../generic/hooks';
88
import { messageTypes } from '../constants';
99
import CompareChangesWidget from '../../library-authoring/component-comparison/CompareChangesWidget';
1010
import { useAcceptLibraryBlockChanges, useIgnoreLibraryBlockChanges } from '../data/apiHooks';
11-
import { useIframe } from '../context/hooks';
11+
import { useIframe } from '../../generic/hooks/context/hooks';
1212
import DeleteModal from '../../generic/delete-modal/DeleteModal';
1313
import messages from './messages';
1414
import { ToastContext } from '../../generic/toast-context';

src/course-unit/sidebar/PublishControls.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { useToggle } from '@openedx/paragon';
44
import { InfoOutline as InfoOutlineIcon } from '@openedx/paragon/icons';
55
import { useIntl } from '@edx/frontend-platform/i18n';
66
import useCourseUnitData from './hooks';
7-
import { useIframe } from '../context/hooks';
7+
import { useIframe } from '../../generic/hooks/context/hooks';
88
import { editCourseUnitVisibilityAndData } from '../data/thunk';
99
import { SidebarBody, SidebarFooter, SidebarHeader } from './components';
1010
import { PUBLISH_TYPES, messageTypes } from '../constants';
Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1 @@
1-
export { useIframeMessages } from './useIframeMessages';
2-
export { useIframeContent } from './useIframeContent';
31
export { useMessageHandlers } from './useMessageHandlers';
4-
export { useIFrameBehavior } from './useIFrameBehavior';
5-
export { useLoadBearingHook } from './useLoadBearingHook';

0 commit comments

Comments
 (0)