diff --git a/services/web/app/src/Features/Project/ProjectController.js b/services/web/app/src/Features/Project/ProjectController.js index 9f22af22ca..e68dd54f34 100644 --- a/services/web/app/src/Features/Project/ProjectController.js +++ b/services/web/app/src/Features/Project/ProjectController.js @@ -881,6 +881,10 @@ const ProjectController = { 'new_navigation_ui', true ), + showPdfDetach: shouldDisplayFeature( + 'pdf_detach', + user.alphaProgram + ), showNewPdfPreview: shouldDisplayFeature( 'new_pdf_preview', user.alphaProgram diff --git a/services/web/app/views/project/editor.pug b/services/web/app/views/project/editor.pug index 5fa6025bed..851a3f185b 100644 --- a/services/web/app/views/project/editor.pug +++ b/services/web/app/views/project/editor.pug @@ -189,6 +189,7 @@ block append meta meta(name="ol-showNewLogsUI" data-type="boolean" content=showNewLogsUI) meta(name="ol-logsUISubvariant" content=logsUISubvariant) meta(name="ol-showSymbolPalette" data-type="boolean" content=showSymbolPalette) + meta(name="ol-showPdfDetach" data-type="boolean" content=showPdfDetach) meta(name="ol-showNewPdfPreview" data-type="boolean" content=showNewPdfPreview) meta(name="ol-enablePdfCaching" data-type="boolean" content=enablePdfCaching) meta(name="ol-trackPdfDownload" data-type="boolean" content=trackPdfDownload) diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index b878821c05..5b61a7e412 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -78,6 +78,8 @@ "duplicate_file": "", "easily_manage_your_project_files_everywhere": "", "editing": "", + "editor_and_pdf": "", + "editor_only_hide_pdf": "", "error": "", "expand": "", "export_project_to_github": "", @@ -165,6 +167,7 @@ "invalid_filename": "", "invalid_request": "", "invite_not_accepted": "", + "layout": "", "learn_how_to_make_documents_compile_quickly": "", "learn_more_about_link_sharing": "", "link_sharing_is_off": "", @@ -237,6 +240,7 @@ "pdf_compile_in_progress_error": "", "pdf_compile_rate_limit_hit": "", "pdf_compile_try_again": "", + "pdf_only_hide_editor": "", "pdf_preview_error": "", "pdf_rendering_error": "", "pdf_viewer_error": "", @@ -253,6 +257,7 @@ "project_approaching_file_limit": "", "project_flagged_too_many_compiles": "", "project_has_too_many_files": "", + "project_layout_sharing_submission": "", "project_not_linked_to_github": "", "project_ownership_transfer_confirmation_1": "", "project_ownership_transfer_confirmation_2": "", @@ -296,6 +301,7 @@ "select_from_output_files": "", "select_from_source_files": "", "select_from_your_computer": "", + "selected": "", "send_first_message": "", "server_error": "", "session_error": "", diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/components/editor-navigation-toolbar-root.js b/services/web/frontend/js/features/editor-navigation-toolbar/components/editor-navigation-toolbar-root.js index 1430a890a3..729cbb4fc6 100644 --- a/services/web/frontend/js/features/editor-navigation-toolbar/components/editor-navigation-toolbar-root.js +++ b/services/web/frontend/js/features/editor-navigation-toolbar/components/editor-navigation-toolbar-root.js @@ -59,6 +59,7 @@ const EditorNavigationToolbarRoot = React.memo( } = useEditorContext(editorContextPropTypes) const { + changeLayout, chatIsOpen, setChatIsOpen, reviewPanelOpen, @@ -93,6 +94,13 @@ const EditorNavigationToolbarRoot = React.memo( setView(view === 'pdf' ? 'editor' : 'pdf') }, [view, setView]) + const handleChangeLayout = useCallback( + (newLayout, newView) => { + changeLayout(newLayout, newView) + }, + [changeLayout] + ) + const openShareModal = useCallback(() => { openShareProjectModal(isProjectOwner) }, [openShareProjectModal, isProjectOwner]) @@ -118,6 +126,7 @@ const EditorNavigationToolbarRoot = React.memo( style={loading ? { display: 'none' } : {}} cobranding={cobranding} onShowLeftMenuClick={onShowLeftMenuClick} + handleChangeLayout={handleChangeLayout} chatIsOpen={chatIsOpen} unreadMessageCount={unreadMessageCount} toggleChatOpen={toggleChatOpen} @@ -139,6 +148,8 @@ const EditorNavigationToolbarRoot = React.memo( pdfButtonIsVisible={pdfLayout === 'flat'} togglePdfView={togglePdfView} trackChangesVisible={trackChangesVisible} + pdfLayout={pdfLayout} + view={view} /> ) } diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/components/icon-editor-only.js b/services/web/frontend/js/features/editor-navigation-toolbar/components/icon-editor-only.js new file mode 100644 index 0000000000..6118050620 --- /dev/null +++ b/services/web/frontend/js/features/editor-navigation-toolbar/components/icon-editor-only.js @@ -0,0 +1,39 @@ +function IconEditorOnly() { + const color = '#505050' // match color from .dropdown-menu > li > a + + return ( + + + + + + + ) +} + +export default IconEditorOnly diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/components/icon-pdf-only.js b/services/web/frontend/js/features/editor-navigation-toolbar/components/icon-pdf-only.js new file mode 100644 index 0000000000..5a487ac6b1 --- /dev/null +++ b/services/web/frontend/js/features/editor-navigation-toolbar/components/icon-pdf-only.js @@ -0,0 +1,25 @@ +function IconPdfOnly() { + const color = '#505050' // match color from .dropdown-menu > li > a + + return ( + + + + + + + ) +} + +export default IconPdfOnly diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/components/layout-dropdown-button.js b/services/web/frontend/js/features/editor-navigation-toolbar/components/layout-dropdown-button.js new file mode 100644 index 0000000000..a971a98be2 --- /dev/null +++ b/services/web/frontend/js/features/editor-navigation-toolbar/components/layout-dropdown-button.js @@ -0,0 +1,89 @@ +import PropTypes from 'prop-types' +import { Dropdown, MenuItem } from 'react-bootstrap' +import { Trans, useTranslation } from 'react-i18next' +import Icon from '../../../shared/components/icon' +import IconChecked from '../../../shared/components/icon-checked' +import ControlledDropdown from '../../../shared/components/controlled-dropdown' +import IconEditorOnly from './icon-editor-only' +import IconPdfOnly from './icon-pdf-only' + +function IconCheckmark({ iconFor, pdfLayout, view }) { + if (iconFor === 'editorOnly' && pdfLayout === 'flat' && view === 'editor') { + return + } else if (iconFor === 'pdfOnly' && pdfLayout === 'flat' && view === 'pdf') { + return + } else if (iconFor === 'sideBySide' && pdfLayout === 'sideBySide') { + return + } + // return empty icon for placeholder + return +} + +function LayoutDropdownButton({ handleChangeLayout, pdfLayout, view }) { + const { t } = useTranslation() + // bsStyle is required for Dropdown.Toggle, but we will override style + return ( + + + + {t('layout')} + + + {t('layout')} + handleChangeLayout('sideBySide')}> + + + {t('editor_and_pdf')} + + handleChangeLayout('flat', 'editor')} + className="menu-item-with-svg" + > + + + , + ]} + /> + + handleChangeLayout('flat', 'pdf')} + className="menu-item-with-svg" + > + + + , + ]} + /> + + + + ) +} + +export default LayoutDropdownButton + +IconCheckmark.propTypes = { + iconFor: PropTypes.string.isRequired, + pdfLayout: PropTypes.string.isRequired, + view: PropTypes.string, +} + +LayoutDropdownButton.propTypes = { + handleChangeLayout: PropTypes.func.isRequired, + pdfLayout: PropTypes.string.isRequired, + view: PropTypes.string, +} diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/components/toolbar-header.js b/services/web/frontend/js/features/editor-navigation-toolbar/components/toolbar-header.js index 3deb5dd16e..7397ea0d83 100644 --- a/services/web/frontend/js/features/editor-navigation-toolbar/components/toolbar-header.js +++ b/services/web/frontend/js/features/editor-navigation-toolbar/components/toolbar-header.js @@ -1,9 +1,11 @@ import React from 'react' import PropTypes from 'prop-types' +import { useTranslation } from 'react-i18next' import MenuButton from './menu-button' import CobrandingLogo from './cobranding-logo' import BackToProjectsButton from './back-to-projects-button' import ChatToggleButton from './chat-toggle-button' +import LayoutDropdownButton from './layout-dropdown-button' import OnlineUsersWidget from './online-users-widget' import ProjectNameEditableLabel from './project-name-editable-label' import TrackChangesToggleButton from './track-changes-toggle-button' @@ -18,6 +20,7 @@ const PublishButton = publishModalModules?.import.default const ToolbarHeader = React.memo(function ToolbarHeader({ cobranding, onShowLeftMenuClick, + handleChangeLayout, chatIsOpen, toggleChatOpen, reviewPanelOpen, @@ -33,17 +36,24 @@ const ToolbarHeader = React.memo(function ToolbarHeader({ renameProject, hasRenamePermissions, openShareModal, + pdfLayout, pdfViewIsOpen, pdfButtonIsVisible, togglePdfView, trackChangesVisible, + view, }) { + const { t } = useTranslation() const shouldDisplayPublishButton = hasPublishPermissions && PublishButton const shouldDisplayTrackChangesButton = trackChangesVisible && !isRestrictedTokenMember return ( -
+
{cobranding && cobranding.logoImgUrl && ( @@ -67,6 +77,14 @@ const ToolbarHeader = React.memo(function ToolbarHeader({
+ {window.showPdfDetach && ( + + )} + {shouldDisplayTrackChangesButton && ( +} + +export default IconChecked diff --git a/services/web/frontend/js/shared/context/layout-context.js b/services/web/frontend/js/shared/context/layout-context.js index 57a5162dc6..5a76550b6c 100644 --- a/services/web/frontend/js/shared/context/layout-context.js +++ b/services/web/frontend/js/shared/context/layout-context.js @@ -64,8 +64,18 @@ export function LayoutProvider({ children }) { }) }, [setPdfLayout, setView]) + const changeLayout = useCallback( + (newLayout, newView) => { + setPdfLayout(newLayout) + setView(newLayout === 'sideBySide' ? 'editor' : newView) + localStorage.setItem('pdf.layout', newLayout) + }, + [setPdfLayout, setView] + ) + const value = useMemo( () => ({ + changeLayout, chatIsOpen, leftMenuShown, pdfLayout, @@ -79,6 +89,7 @@ export function LayoutProvider({ children }) { view, }), [ + changeLayout, chatIsOpen, leftMenuShown, pdfLayout, diff --git a/services/web/frontend/stylesheets/app/editor/toolbar.less b/services/web/frontend/stylesheets/app/editor/toolbar.less index 7733273633..4e3b32e4ae 100644 --- a/services/web/frontend/stylesheets/app/editor/toolbar.less +++ b/services/web/frontend/stylesheets/app/editor/toolbar.less @@ -7,7 +7,8 @@ border-bottom: @toolbar-border-bottom; > a, - .toolbar-right > a { + .toolbar-right > a, + button { position: relative; .label { position: absolute; @@ -19,6 +20,13 @@ } } + .toolbar-right { + button { + background: transparent; + box-shadow: none; + } + } + > a:focus { outline: none; } @@ -151,6 +159,20 @@ } } +#layout-dropdown { + // override style added by required bsStyle react-bootstrap prop + text-decoration: none !important; +} + +#layout-dropdown-list { + a { + i, + svg { + margin-right: @margin-xs; + } + } +} + .header-cobranding-logo { display: block; width: auto; @@ -375,3 +397,28 @@ .opacity(0.65); .box-shadow(none); } + +.menu-item-with-svg { + svg { + line, + rect { + stroke: @dropdown-link-color; + } + path { + fill: @dropdown-link-color; + } + } + + a:hover, + a:focus { + svg { + line, + rect { + stroke: @dropdown-link-hover-color; + } + path { + fill: @dropdown-link-hover-color; + } + } + } +} diff --git a/services/web/locales/en.json b/services/web/locales/en.json index 943c465b17..4b3b110763 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -1523,5 +1523,11 @@ "showing_x_results": "Showing __x__ results", "showing_1_result": "Showing 1 result", "showing_1_result_of_total": "Showing 1 result of __total__", - "history_resync": "History Resync" + "history_resync": "History Resync", + "layout": "Layout", + "editor_and_pdf": "Editor & PDF", + "editor_only_hide_pdf": "Editor only <0>(hide PDF)", + "pdf_only_hide_editor": "PDF only <0>(hide editor)", + "selected": "Selected", + "project_layout_sharing_submission": "Project Layout, Sharing, and Submission" } diff --git a/services/web/test/frontend/features/editor-navigation-toolbar/components/layout-dropdown-button.test.js b/services/web/test/frontend/features/editor-navigation-toolbar/components/layout-dropdown-button.test.js new file mode 100644 index 0000000000..502318dcf6 --- /dev/null +++ b/services/web/test/frontend/features/editor-navigation-toolbar/components/layout-dropdown-button.test.js @@ -0,0 +1,23 @@ +import { render, screen } from '@testing-library/react' +import LayoutDropdownButton from '../../../../../frontend/js/features/editor-navigation-toolbar/components/layout-dropdown-button' + +describe('', function () { + const defaultProps = { + handleChangeLayout: () => {}, + pdfLayout: 'flat', + view: 'editor', + } + + it('should mark current layout option as selected (visually by checkmark, and aria-label for accessibility)', function () { + render() + screen.getByRole('menuitem', { + name: 'Editor & PDF', + }) + screen.getByRole('menuitem', { + name: 'PDF only (hide editor)', + }) + screen.getByRole('menuitem', { + name: 'Selected Editor only (hide PDF)', + }) + }) +})