Skip to content

feat: library unit page skeleton [FC-0083] #1779

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 34 commits into from
Apr 11, 2025
Merged
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
7c52f85
feat: library unit page skeleton
navinkarkera Mar 26, 2025
f843e9f
feat: iframe auto height adjust
navinkarkera Mar 28, 2025
e0ac75d
fixup! feat: iframe auto height adjust
navinkarkera Apr 3, 2025
3e26373
refactor: iframe ref and event handler
navinkarkera Apr 3, 2025
b589401
feat: component draggable cards
navinkarkera Apr 3, 2025
324b898
refactor: unit page style
navinkarkera Apr 4, 2025
5a6aceb
feat: component tags and draft status in unit pages
navinkarkera Apr 4, 2025
64d81e6
feat: show component sidebar on selection
navinkarkera Apr 7, 2025
327a4b0
refactor: url update on component selection in unit page
navinkarkera Apr 7, 2025
328777d
feat: hide preview tab in sidebar in unit page
navinkarkera Apr 7, 2025
a616b50
feat: hover state for components in unit page
navinkarkera Apr 7, 2025
ba26a9f
fix: clicks not being passed to card children when in focus
navinkarkera Apr 7, 2025
68bca46
fix: drag-n-drop components in unit page
navinkarkera Apr 7, 2025
d8cb916
feat: open unit page from card
navinkarkera Apr 8, 2025
52bd445
feat: connect sidebar to unit page
navinkarkera Apr 8, 2025
af2f301
feat: unit components preview in sidebar
navinkarkera Apr 8, 2025
bd4a309
feat: open library sidebar on unit info click
navinkarkera Apr 8, 2025
9dda74d
chore: fix lint issues
navinkarkera Apr 8, 2025
69997ff
test: fix useIframeBehaviour hook tests
navinkarkera Apr 8, 2025
95bb986
fix: compare block preview component
navinkarkera Apr 8, 2025
d6be6fa
fix: useContainer api usage
navinkarkera Apr 8, 2025
42b00b9
test: fix failing tests
navinkarkera Apr 8, 2025
d20d3e9
test: fix failing tests
navinkarkera Apr 9, 2025
0fd119a
fix: resolve conflicts
navinkarkera Apr 9, 2025
34b7e6b
fix: failing tests
navinkarkera Apr 9, 2025
7797aa9
test: library unit page
navinkarkera Apr 9, 2025
49bba7b
test: fix failing tests
navinkarkera Apr 9, 2025
a68aec7
fix: hide open button in unit sidebar if in unit page
navinkarkera Apr 9, 2025
56003bf
fix: unit routes
navinkarkera Apr 10, 2025
a9b885a
refactor: rename disabledTabs to hiddenTabs
navinkarkera Apr 10, 2025
7daad72
refactor: iframe min height and useIframeBehaviour spelling
navinkarkera Apr 11, 2025
8bb3fd2
fix: resolve rebase conflicts
navinkarkera Apr 11, 2025
ea853aa
refactor: set iframe min height to 200px
navinkarkera Apr 11, 2025
2d4e4b1
fix: Increate editor minH to 700px to fix advanced editors
ChrisChV Apr 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/CourseAuthoringRoutes.jsx
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@ import ScheduleAndDetails from './schedule-and-details';
import { GradingSettings } from './grading-settings';
import CourseTeam from './course-team/CourseTeam';
import { CourseUpdates } from './course-updates';
import { CourseUnit, IframeProvider } from './course-unit';
import { CourseUnit } from './course-unit';
import { Certificates } from './certificates';
import CourseExportPage from './export-page/CourseExportPage';
import CourseOptimizerPage from './optimizer-page/CourseOptimizerPage';
@@ -26,6 +26,7 @@ import { DECODED_ROUTES } from './constants';
import CourseChecklist from './course-checklist';
import GroupConfigurations from './group-configurations';
import { CourseLibraries } from './course-libraries';
import { IframeProvider } from './generic/hooks/context/iFrameContext';

/**
* As of this writing, these routes are mounted at a path prefixed with the following:
14 changes: 14 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
@@ -92,3 +92,17 @@ export const REGEX_RULES = {
export const IFRAME_FEATURE_POLICY = (
'microphone *; camera *; midi *; geolocation *; encrypted-media *; clipboard-write *'
);

export const iframeStateKeys = {
iframeHeight: 'iframeHeight',
hasLoaded: 'hasLoaded',
showError: 'showError',
windowTopOffset: 'windowTopOffset',
};

export const iframeMessageTypes = {
modal: 'plugin.modal',
resize: 'plugin.resize',
videoFullScreen: 'plugin.videoFullScreen',
xblockEvent: 'xblock-event',
};
2 changes: 1 addition & 1 deletion src/course-unit/CourseUnit.test.jsx
Original file line number Diff line number Diff line change
@@ -59,7 +59,7 @@ import configureModalMessages from '../generic/configure-modal/messages';
import { getContentTaxonomyTagsApiUrl, getContentTaxonomyTagsCountApiUrl } from '../content-tags-drawer/data/api';
import addComponentMessages from './add-component/messages';
import { messageTypes, PUBLISH_TYPES, UNIT_VISIBILITY_STATES } from './constants';
import { IframeProvider } from './context/iFrameContext';
import { IframeProvider } from '../generic/hooks/context/iFrameContext';
import moveModalMessages from './move-modal/messages';
import xblockContainerIframeMessages from './xblock-container-iframe/messages';
import headerNavigationsMessages from './header-navigations/messages';
2 changes: 1 addition & 1 deletion src/course-unit/add-component/AddComponent.jsx
Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@ import AddComponentButton from './add-component-btn';
import messages from './messages';
import { ComponentPicker } from '../../library-authoring/component-picker';
import { messageTypes } from '../constants';
import { useIframe } from '../context/hooks';
import { useIframe } from '../../generic/hooks/context/hooks';
import { useEventListener } from '../../generic/hooks';

const AddComponent = ({
4 changes: 2 additions & 2 deletions src/course-unit/add-component/AddComponent.test.jsx
Original file line number Diff line number Diff line change
@@ -18,7 +18,7 @@ import { courseSectionVerticalMock } from '../__mocks__';
import { COMPONENT_TYPES } from '../../generic/block-type-utils/constants';
import AddComponent from './AddComponent';
import messages from './messages';
import { IframeProvider } from '../context/iFrameContext';
import { IframeProvider } from '../../generic/hooks/context/iFrameContext';
import { messageTypes } from '../constants';

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

const mockSendMessageToIframe = jest.fn();
jest.mock('../context/hooks', () => ({
jest.mock('../../generic/hooks/context/hooks', () => ({
useIframe: () => ({
sendMessageToIframe: mockSendMessageToIframe,
}),
10 changes: 0 additions & 10 deletions src/course-unit/constants.js
Original file line number Diff line number Diff line change
@@ -39,17 +39,7 @@ export const getXBlockSupportMessages = (intl) => ({
},
});

export const stateKeys = {
iframeHeight: 'iframeHeight',
hasLoaded: 'hasLoaded',
showError: 'showError',
windowTopOffset: 'windowTopOffset',
};

export const messageTypes = {
modal: 'plugin.modal',
resize: 'plugin.resize',
videoFullScreen: 'plugin.videoFullScreen',
refreshXBlock: 'refreshXBlock',
showMoveXBlockModal: 'showMoveXBlockModal',
completeXBlockMoving: 'completeXBlockMoving',
6 changes: 3 additions & 3 deletions src/course-unit/hooks.jsx
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ import { camelCaseObject } from '@edx/frontend-platform/utils';
import { RequestStatus } from '../data/constants';
import { useClipboard } from '../generic/clipboard';
import { useEventListener } from '../generic/hooks';
import { COURSE_BLOCK_NAMES } from '../constants';
import { COURSE_BLOCK_NAMES, iframeMessageTypes } from '../constants';
import { messageTypes, PUBLISH_TYPES } from './constants';
import {
createNewCourseXBlock,
@@ -41,7 +41,7 @@ import {
updateMovedXBlockParams,
updateQueryPendingStatus,
} from './data/slice';
import { useIframe } from './context/hooks';
import { useIframe } from '../generic/hooks/context/hooks';

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

const handleMessage = useCallback((event) => {
if (event.data?.type === messageTypes.resize) {
if (event.data?.type === iframeMessageTypes.resize) {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
14 changes: 7 additions & 7 deletions src/course-unit/hooks.test.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import { act, renderHook } from '@testing-library/react';
import { useScrollToLastPosition, useLayoutGrid } from './hooks';
import { messageTypes } from './constants';
import { iframeMessageTypes } from '../constants';

jest.useFakeTimers();

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

act(() => {
window.dispatchEvent(new MessageEvent('message', { data: { type: messageTypes.resize } }));
window.dispatchEvent(new MessageEvent('message', { data: { type: iframeMessageTypes.resize } }));
jest.advanceTimersByTime(1000);
});

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

act(() => {
window.dispatchEvent(new MessageEvent('message', { data: { type: messageTypes.resize } }));
window.dispatchEvent(new MessageEvent('message', { data: { type: messageTypes.resize } }));
window.dispatchEvent(new MessageEvent('message', { data: { type: iframeMessageTypes.resize } }));
window.dispatchEvent(new MessageEvent('message', { data: { type: iframeMessageTypes.resize } }));
});

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

act(() => {
window.dispatchEvent(new MessageEvent('message', { data: { type: messageTypes.resize } }));
window.dispatchEvent(new MessageEvent('message', { data: { type: iframeMessageTypes.resize } }));
jest.advanceTimersByTime(500);
window.dispatchEvent(new MessageEvent('message', { data: { type: messageTypes.resize } }));
window.dispatchEvent(new MessageEvent('message', { data: { type: iframeMessageTypes.resize } }));
});

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

act(() => {
window.dispatchEvent(new MessageEvent('message', { data: { type: messageTypes.resize } }));
window.dispatchEvent(new MessageEvent('message', { data: { type: iframeMessageTypes.resize } }));
jest.advanceTimersByTime(1000);
});

1 change: 0 additions & 1 deletion src/course-unit/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export { default as CourseUnit } from './CourseUnit';
export { IframeProvider } from './context/iFrameContext';
2 changes: 1 addition & 1 deletion src/course-unit/move-modal/hooks.tsx
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@ import { RequestStatus } from '../../data/constants';
import { useEventListener } from '../../generic/hooks';
import { getCourseOutlineInfo, getCourseOutlineInfoLoadingStatus } from '../data/selectors';
import { getCourseOutlineInfoQuery, patchUnitItemQuery } from '../data/thunk';
import { useIframe } from '../context/hooks';
import { useIframe } from '../../generic/hooks/context/hooks';
import { messageTypes } from '../constants';
import { CATEGORIES, MOVE_DIRECTIONS } from './constants';
import {
2 changes: 1 addition & 1 deletion src/course-unit/move-modal/moveModal.test.tsx
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@ import { getCourseOutlineInfoUrl } from '../data/api';
import { courseOutlineInfoMock } from '../__mocks__';
import { executeThunk } from '../../utils';
import { getCourseOutlineInfoQuery } from '../data/thunk';
import { IframeProvider } from '../context/iFrameContext';
import { IframeProvider } from '../../generic/hooks/context/iFrameContext';
import { IXBlock } from './interfaces';
import MoveModal from './index';
import messages from './messages';
9 changes: 4 additions & 5 deletions src/course-unit/preview-changes/index.test.tsx
Original file line number Diff line number Diff line change
@@ -10,7 +10,6 @@ import {

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

const mockSendMessageToIframe = jest.fn();
jest.mock('../context/hooks', () => ({
jest.mock('../../generic/hooks/context/hooks', () => ({
useIframe: () => ({
iframeRef: { current: { contentWindow: {} as HTMLIFrameElement } },
setIframeRef: () => {},
sendMessageToIframe: mockSendMessageToIframe,
}),
}));
const render = (eventData?: LibraryChangesMessageData) => {
baseRender(<IframePreviewLibraryXBlockChanges />, {
extraWrapper: ({ children }) => <IframeProvider>{ children }</IframeProvider>,
});
baseRender(<IframePreviewLibraryXBlockChanges />);
const message = {
data: {
type: messageTypes.showXBlockLibraryChangesPreview,
2 changes: 1 addition & 1 deletion src/course-unit/preview-changes/index.tsx
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ import { useEventListener } from '../../generic/hooks';
import { messageTypes } from '../constants';
import CompareChangesWidget from '../../library-authoring/component-comparison/CompareChangesWidget';
import { useAcceptLibraryBlockChanges, useIgnoreLibraryBlockChanges } from '../data/apiHooks';
import { useIframe } from '../context/hooks';
import { useIframe } from '../../generic/hooks/context/hooks';
import DeleteModal from '../../generic/delete-modal/DeleteModal';
import messages from './messages';
import { ToastContext } from '../../generic/toast-context';
2 changes: 1 addition & 1 deletion src/course-unit/sidebar/PublishControls.jsx
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ import { useToggle } from '@openedx/paragon';
import { InfoOutline as InfoOutlineIcon } from '@openedx/paragon/icons';
import { useIntl } from '@edx/frontend-platform/i18n';
import useCourseUnitData from './hooks';
import { useIframe } from '../context/hooks';
import { useIframe } from '../../generic/hooks/context/hooks';
import { editCourseUnitVisibilityAndData } from '../data/thunk';
import { SidebarBody, SidebarFooter, SidebarHeader } from './components';
import { PUBLISH_TYPES, messageTypes } from '../constants';
4 changes: 0 additions & 4 deletions src/course-unit/xblock-container-iframe/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
export { useIframeMessages } from './useIframeMessages';
export { useIframeContent } from './useIframeContent';
export { useMessageHandlers } from './useMessageHandlers';
export { useIFrameBehavior } from './useIFrameBehavior';
export { useLoadBearingHook } from './useLoadBearingHook';
Loading