diff --git a/.env b/.env index 7b9229b9a3..9ed2d75cca 100644 --- a/.env +++ b/.env @@ -37,8 +37,7 @@ ENABLE_VIDEO_UPLOAD_PAGE_LINK_IN_CONTENT_DROPDOWN=false ENABLE_TAGGING_TAXONOMY_PAGES=true ENABLE_CERTIFICATE_PAGE=true ENABLE_COURSE_IMPORT_IN_LIBRARY=false -ENABLE_UNIT_PAGE_NEW_DESIGN=false -ENABLE_COURSE_OUTLINE_NEW_DESIGN=false +ENABLE_UNIT_PAGE_NEW_DESIGN=true BBB_LEARN_MORE_URL='' HOTJAR_APP_ID='' HOTJAR_VERSION=6 diff --git a/.env.development b/.env.development index 9e25a53b35..d2d4e86357 100644 --- a/.env.development +++ b/.env.development @@ -38,7 +38,6 @@ ENABLE_ASSETS_PAGE=false ENABLE_VIDEO_UPLOAD_PAGE_LINK_IN_CONTENT_DROPDOWN=true ENABLE_CERTIFICATE_PAGE=true ENABLE_COURSE_IMPORT_IN_LIBRARY=true -ENABLE_COURSE_OUTLINE_NEW_DESIGN=true ENABLE_UNIT_PAGE_NEW_DESIGN=true ENABLE_NEW_VIDEO_UPLOAD_PAGE=true ENABLE_TAGGING_TAXONOMY_PAGES=true diff --git a/.env.test b/.env.test index 617eacb32f..eef08000c5 100644 --- a/.env.test +++ b/.env.test @@ -34,8 +34,7 @@ ENABLE_ASSETS_PAGE=false ENABLE_VIDEO_UPLOAD_PAGE_LINK_IN_CONTENT_DROPDOWN=true ENABLE_CERTIFICATE_PAGE=true ENABLE_COURSE_IMPORT_IN_LIBRARY=true -ENABLE_COURSE_OUTLINE_NEW_DESIGN=false -ENABLE_UNIT_PAGE_NEW_DESIGN=false +ENABLE_UNIT_PAGE_NEW_DESIGN=true ENABLE_TAGGING_TAXONOMY_PAGES=true BBB_LEARN_MORE_URL='' INVITE_STUDENTS_EMAIL_TO="someone@domain.com" diff --git a/src/course-outline/CourseOutline.tsx b/src/course-outline/CourseOutline.tsx index 1bfc50571a..d888b485c4 100644 --- a/src/course-outline/CourseOutline.tsx +++ b/src/course-outline/CourseOutline.tsx @@ -247,7 +247,7 @@ const CourseOutline = () => { handleVideoSharingOptionChange={handleVideoSharingOptionChange} />
-
+
diff --git a/src/course-unit/CourseUnit.test.tsx b/src/course-unit/CourseUnit.test.tsx index 953980eb86..a72ed30e3b 100644 --- a/src/course-unit/CourseUnit.test.tsx +++ b/src/course-unit/CourseUnit.test.tsx @@ -149,6 +149,7 @@ const RootWrapper = () => ( describe('', () => { beforeEach(async () => { const mocks = initializeMocks(); + window.scrollTo = jest.fn(); global.localStorage.clear(); store = mocks.reduxStore; @@ -180,6 +181,10 @@ describe('', () => { }); it('render CourseUnit component correctly', async () => { + setConfig({ + ...getConfig(), + ENABLE_UNIT_PAGE_NEW_DESIGN: false, + }); render(); const currentSectionName = courseSectionVerticalMock.xblock_info.ancestor_info.ancestors[1].display_name; const currentSubSectionName = courseSectionVerticalMock.xblock_info.ancestor_info.ancestors[1].display_name; @@ -271,6 +276,10 @@ describe('', () => { }); it('closes legacy edit modal and updates course unit sidebar after saveEditedXBlockData message', async () => { + setConfig({ + ...getConfig(), + ENABLE_UNIT_PAGE_NEW_DESIGN: false, + }); render(); const xblocksIframe = await screen.findByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); @@ -308,6 +317,10 @@ describe('', () => { }); it('updates course unit sidebar after receiving refreshPositions message', async () => { + setConfig({ + ...getConfig(), + ENABLE_UNIT_PAGE_NEW_DESIGN: false, + }); render(); const xblocksIframe = await screen.findByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); @@ -341,6 +354,10 @@ describe('', () => { it('checks whether xblock is removed when the corresponding delete button is clicked and the sidebar is the updated', async () => { const user = userEvent.setup(); + setConfig({ + ...getConfig(), + ENABLE_UNIT_PAGE_NEW_DESIGN: false, + }); render(); const iframe = await screen.findByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage); @@ -513,6 +530,10 @@ describe('', () => { }); it('checks if xblock is a duplicate when the corresponding duplicate button is clicked and if the sidebar status is updated', async () => { + setConfig({ + ...getConfig(), + ENABLE_UNIT_PAGE_NEW_DESIGN: false, + }); axiosMock .onGet(getCourseSectionVerticalApiUrl(blockId)) .reply(200, { @@ -1130,6 +1151,10 @@ describe('', () => { }); it('renders course unit details for a draft with unpublished changes', async () => { + setConfig({ + ...getConfig(), + ENABLE_UNIT_PAGE_NEW_DESIGN: false, + }); render(); await waitFor(() => { @@ -1206,6 +1231,10 @@ describe('', () => { it('should toggle visibility from sidebar and update course unit state accordingly', async () => { const user = userEvent.setup(); + setConfig({ + ...getConfig(), + ENABLE_UNIT_PAGE_NEW_DESIGN: false, + }); render(); const courseUnitSidebar = await screen.findByTestId('course-unit-sidebar'); @@ -1298,6 +1327,10 @@ describe('', () => { it('should publish course unit after click on the "Publish" button', async () => { const user = userEvent.setup(); + setConfig({ + ...getConfig(), + ENABLE_UNIT_PAGE_NEW_DESIGN: false, + }); render(); let courseUnitSidebar; let publishBtn; @@ -1350,6 +1383,10 @@ describe('', () => { it('should discard changes after click on the Discard changes button', async () => { const user = userEvent.setup(); + setConfig({ + ...getConfig(), + ENABLE_UNIT_PAGE_NEW_DESIGN: false, + }); render(); let courseUnitSidebar; let discardChangesBtn; @@ -1425,6 +1462,10 @@ describe('', () => { }); it('should toggle visibility from header configure modal and update course unit state accordingly', async () => { + setConfig({ + ...getConfig(), + ENABLE_UNIT_PAGE_NEW_DESIGN: false, + }); const user = userEvent.setup(); render(); expect( @@ -1524,6 +1565,13 @@ describe('', () => { }); describe('Copy paste functionality', () => { + beforeEach(() => { + setConfig({ + ...getConfig(), + ENABLE_UNIT_PAGE_NEW_DESIGN: false, + }); + }); + it('should copy a unit, paste it as a new unit, and update the course section vertical data', async () => { const user = userEvent.setup(); render(); @@ -2320,17 +2368,31 @@ describe('', () => { }); }); - it('should display visibility modal correctly', async () => ( - checkRenderVisibilityModal('libraryContentAccess') - )); + it('should display visibility modal correctly', async () => { + setConfig({ + ...getConfig(), + ENABLE_UNIT_PAGE_NEW_DESIGN: false, + }); + await checkRenderVisibilityModal('libraryContentAccess'); + }); - it('opens legacy edit modal on edit button click', checkLegacyEditModalOnEditMessage); + it('opens legacy edit modal on edit button click', async () => { + setConfig({ + ...getConfig(), + ENABLE_UNIT_PAGE_NEW_DESIGN: false, + }); + await checkLegacyEditModalOnEditMessage(); + }); }); describe('Split Test Content page', () => { const newUnitId = '12345'; beforeEach(async () => { + setConfig({ + ...getConfig(), + ENABLE_UNIT_PAGE_NEW_DESIGN: false, + }); axiosMock .onGet(getCourseSectionVerticalApiUrl(blockId)) .reply(200, { @@ -2524,6 +2586,7 @@ describe('', () => { setConfig({ ...getConfig(), ENABLE_TAGGING_TAXONOMY_PAGES: 'true', + ENABLE_UNIT_PAGE_NEW_DESIGN: false, }); render(); diff --git a/src/course-unit/CourseUnit.tsx b/src/course-unit/CourseUnit.tsx index 5e4c879dd0..3e4e7bcfc0 100644 --- a/src/course-unit/CourseUnit.tsx +++ b/src/course-unit/CourseUnit.tsx @@ -333,7 +333,7 @@ const CourseUnit = () => { showPasteUnit={showPasteUnit} /> )} -
+
{currentlyVisibleToStudents && ( ', () => { }); it('click Add button should open add sidebar', async () => { - setConfig({ - ...getConfig(), - ENABLE_UNIT_PAGE_NEW_DESIGN: 'true', - }); - const user = userEvent.setup(); renderComponent({ unitCategory: COURSE_BLOCK_NAMES.vertical.id }); diff --git a/src/course-unit/header-title/HeaderTitle.test.tsx b/src/course-unit/header-title/HeaderTitle.test.tsx index 36281e5c8e..ef7511e3bf 100644 --- a/src/course-unit/header-title/HeaderTitle.test.tsx +++ b/src/course-unit/header-title/HeaderTitle.test.tsx @@ -1,5 +1,6 @@ import MockAdapter from 'axios-mock-adapter'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; +import { getConfig, setConfig } from '@edx/frontend-platform'; import { initializeMocks, render, screen } from '@src/testUtils'; import userEvent from '@testing-library/user-event'; import { executeThunk } from '@src/utils'; @@ -47,6 +48,10 @@ describe('', () => { }); it('render HeaderTitle component correctly', () => { + setConfig({ + ...getConfig(), + ENABLE_UNIT_PAGE_NEW_DESIGN: false, + }); renderComponent(); expect(screen.getByText(unitTitle)).toBeInTheDocument(); @@ -55,6 +60,10 @@ describe('', () => { }); it('render HeaderTitle with open edit form', () => { + setConfig({ + ...getConfig(), + ENABLE_UNIT_PAGE_NEW_DESIGN: false, + }); renderComponent({ isTitleEditFormOpen: true, }); @@ -66,6 +75,10 @@ describe('', () => { }); it('Units sourced from upstream show a enabled edit button', async () => { + setConfig({ + ...getConfig(), + ENABLE_UNIT_PAGE_NEW_DESIGN: false, + }); // Override mock unit with one sourced from an upstream library axiosMock = new MockAdapter(getAuthenticatedHttpClient()); axiosMock diff --git a/src/course-unit/unit-sidebar/UnitSidebarContext.tsx b/src/course-unit/unit-sidebar/UnitSidebarContext.tsx index e8e76c4b7f..34517a0327 100644 --- a/src/course-unit/unit-sidebar/UnitSidebarContext.tsx +++ b/src/course-unit/unit-sidebar/UnitSidebarContext.tsx @@ -29,9 +29,10 @@ interface UnitSidebarContextData { readOnly: boolean; /* * There are other blocks that use the same unit screen and sidebars. - * For example: Conditional block. + * For example: Conditional block, Content Experiments block. */ isVertical: boolean; + currentItemCategory?: string; } const UnitSidebarContext = createContext(undefined); @@ -55,7 +56,8 @@ export const UnitSidebarProvider = ({ const [isOpen, open, , toggle] = useToggle(true); const currentItemData = useSelector(getCourseUnitData); - const isVertical = currentItemData?.category === 'vertical'; + const currentItemCategory = currentItemData?.category; + const isVertical = currentItemCategory === 'vertical'; const setCurrentPageKey = useCallback(/* istanbul ignore next */ ( pageKey?: UnitSidebarPageKeys, @@ -92,6 +94,7 @@ export const UnitSidebarProvider = ({ toggle, readOnly, isVertical, + currentItemCategory, }), [ currentPageKey, @@ -105,6 +108,7 @@ export const UnitSidebarProvider = ({ toggle, readOnly, isVertical, + currentItemCategory, ], ); diff --git a/src/course-unit/unit-sidebar/UnitSidebarPagesContext.tsx b/src/course-unit/unit-sidebar/UnitSidebarPagesContext.tsx index 3054f622c0..18b821a841 100644 --- a/src/course-unit/unit-sidebar/UnitSidebarPagesContext.tsx +++ b/src/course-unit/unit-sidebar/UnitSidebarPagesContext.tsx @@ -16,7 +16,7 @@ export type UnitSidebarPages = { align?: SidebarPage; }; -const getUnitSidebarPages = (readOnly: boolean, hasComponentSelected: boolean) => { +const getUnitSidebarPages = (readOnly: boolean, disableAdd: boolean) => { const showAlignSidebar = getConfig().ENABLE_TAGGING_TAXONOMY_PAGES === 'true'; return { @@ -30,8 +30,8 @@ const getUnitSidebarPages = (readOnly: boolean, hasComponentSelected: boolean) = component: AddSidebar, icon: Plus, title: messages.sidebarButtonAdd, - disabled: hasComponentSelected, - tooltip: hasComponentSelected ? messages.sidebarDisabledAddTooltip : undefined, + disabled: disableAdd, + tooltip: disableAdd ? messages.sidebarDisabledAddTooltip : undefined, }, }), ...(showAlignSidebar && { @@ -81,13 +81,19 @@ type UnitSidebarPagesProviderProps = { }; export const UnitSidebarPagesProvider = ({ children }: UnitSidebarPagesProviderProps) => { - const { readOnly, selectedComponentId } = useUnitSidebarContext(); + const { + readOnly, + selectedComponentId, + currentItemCategory, + } = useUnitSidebarContext(); const hasComponentSelected = selectedComponentId !== undefined; + const isSplitTest = currentItemCategory === 'split_test'; + const disableAdd = hasComponentSelected || isSplitTest; const sidebarPages = useMemo( - () => getUnitSidebarPages(readOnly, hasComponentSelected), - [readOnly, hasComponentSelected], + () => getUnitSidebarPages(readOnly, disableAdd), + [readOnly, disableAdd], ); return ( diff --git a/src/course-unit/utils.ts b/src/course-unit/utils.ts index 973f053686..cb15e6a87f 100644 --- a/src/course-unit/utils.ts +++ b/src/course-unit/utils.ts @@ -47,5 +47,5 @@ export const subsectionFirstUnitEditUrl = ( }; export const isUnitPageNewDesignEnabled = () => ( - getConfig().ENABLE_UNIT_PAGE_NEW_DESIGN?.toString().toLowerCase() === 'true' + (getConfig().ENABLE_UNIT_PAGE_NEW_DESIGN?.toString().toLowerCase() ?? 'true') === 'true' ); diff --git a/src/generic/resizable/Resizable.tsx b/src/generic/resizable/Resizable.tsx index adfe1606bb..d484a1bea9 100644 --- a/src/generic/resizable/Resizable.tsx +++ b/src/generic/resizable/Resizable.tsx @@ -62,13 +62,13 @@ export const ResizableBox = ({ return (
{/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex, jsx-a11y/no-static-element-interactions */}
-
+
{children}
diff --git a/src/generic/sidebar/Sidebar.tsx b/src/generic/sidebar/Sidebar.tsx index ae3c0e3cc7..35a83739a1 100644 --- a/src/generic/sidebar/Sidebar.tsx +++ b/src/generic/sidebar/Sidebar.tsx @@ -96,7 +96,7 @@ export function Sidebar({ const activeKey = isOpen ? currentPageKey : undefined; return ( - + {(isOpen && !!currentPageKey) ? ( diff --git a/src/generic/sidebar/index.scss b/src/generic/sidebar/index.scss index 88b6dd101f..9b09753138 100644 --- a/src/generic/sidebar/index.scss +++ b/src/generic/sidebar/index.scss @@ -2,14 +2,16 @@ position: sticky; top: 0; align-self: flex-start; + height: 100vh; max-height: 100vh; + display: flex; + flex-direction: column; + margin-left: 1rem; .sidebar-content { flex: 0 1 auto; min-width: 440px; - overflow-y: auto; - height: 100vh; - max-height: 100vh; + overflow: hidden auto; /* * Change the styles for tabs in all sidebars. diff --git a/src/index.jsx b/src/index.jsx index b62a9e58cf..4d4d6d2c73 100755 --- a/src/index.jsx +++ b/src/index.jsx @@ -184,8 +184,7 @@ initialize({ process.env.ENABLE_VIDEO_UPLOAD_PAGE_LINK_IN_CONTENT_DROPDOWN || 'false', ENABLE_CERTIFICATE_PAGE: process.env.ENABLE_CERTIFICATE_PAGE || 'false', ENABLE_COURSE_IMPORT_IN_LIBRARY: process.env.ENABLE_COURSE_IMPORT_IN_LIBRARY || 'false', - ENABLE_UNIT_PAGE_NEW_DESIGN: process.env.ENABLE_UNIT_PAGE_NEW_DESIGN || 'false', - ENABLE_COURSE_OUTLINE_NEW_DESIGN: process.env.ENABLE_COURSE_OUTLINE_NEW_DESIGN || 'false', + ENABLE_UNIT_PAGE_NEW_DESIGN: process.env.ENABLE_UNIT_PAGE_NEW_DESIGN || 'true', ENABLE_TAGGING_TAXONOMY_PAGES: process.env.ENABLE_TAGGING_TAXONOMY_PAGES || 'false', ENABLE_CHECKLIST_QUALITY: process.env.ENABLE_CHECKLIST_QUALITY || 'true', ENABLE_GRADING_METHOD_IN_PROBLEMS: process.env.ENABLE_GRADING_METHOD_IN_PROBLEMS === 'true', diff --git a/src/plugin-slots/CourseAuthoringOutlineSidebarSlot/index.tsx b/src/plugin-slots/CourseAuthoringOutlineSidebarSlot/index.tsx index da5c54e1cc..9f7d947bfd 100644 --- a/src/plugin-slots/CourseAuthoringOutlineSidebarSlot/index.tsx +++ b/src/plugin-slots/CourseAuthoringOutlineSidebarSlot/index.tsx @@ -7,17 +7,19 @@ export const CourseAuthoringOutlineSidebarSlot = ({ courseName, sections, }: CourseAuthoringOutlineSidebarSlotProps) => ( - - - +
+ + + +
); type Section = { diff --git a/src/plugin-slots/CourseAuthoringUnitSidebarSlot/index.tsx b/src/plugin-slots/CourseAuthoringUnitSidebarSlot/index.tsx index 67575020ca..2a37c0a993 100644 --- a/src/plugin-slots/CourseAuthoringUnitSidebarSlot/index.tsx +++ b/src/plugin-slots/CourseAuthoringUnitSidebarSlot/index.tsx @@ -1,5 +1,8 @@ import { PluginSlot } from '@openedx/frontend-plugin-framework/dist'; +import classNames from 'classnames'; + import { UnitSidebar } from '@src/course-unit/unit-sidebar/UnitSidebar'; +import { isUnitPageNewDesignEnabled } from '@src/course-unit/utils'; export const CourseAuthoringUnitSidebarSlot = ( { @@ -12,28 +15,32 @@ export const CourseAuthoringUnitSidebarSlot = ( isSplitTestType, }: CourseAuthoringUnitSidebarSlotProps, ) => ( - - - + > + + +
); type XBlock = {