mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-29 16:03:37 -05:00
Merge pull request #5630 from overleaf/jel-layout-dropdown
New project layout dropdown GitOrigin-RevId: 8d7f4ff6649fe249b762642e70522597e5e78dd4
This commit is contained in:
parent
cc78541714
commit
3ad686c30b
13 changed files with 296 additions and 3 deletions
|
@ -881,6 +881,10 @@ const ProjectController = {
|
|||
'new_navigation_ui',
|
||||
true
|
||||
),
|
||||
showPdfDetach: shouldDisplayFeature(
|
||||
'pdf_detach',
|
||||
user.alphaProgram
|
||||
),
|
||||
showNewPdfPreview: shouldDisplayFeature(
|
||||
'new_pdf_preview',
|
||||
user.alphaProgram
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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": "",
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
function IconEditorOnly() {
|
||||
const color = '#505050' // match color from .dropdown-menu > li > a
|
||||
|
||||
return (
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect
|
||||
x="0.5"
|
||||
y="1.5769"
|
||||
width="15"
|
||||
height="13.7692"
|
||||
rx="1.5"
|
||||
stroke={color}
|
||||
/>
|
||||
<line x1="1" y1="2.49976" x2="15" y2="2.49976" stroke={color} />
|
||||
<line
|
||||
x1="14"
|
||||
y1="2.99976"
|
||||
x2="14"
|
||||
y2="14.9998"
|
||||
stroke={color}
|
||||
strokeWidth="2"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M7.74946 5.26853L10.7397 8.35194C11.0868 8.70989 11.0868 9.29025 10.7397 9.6482L7.74946 12.7316C7.40233 13.0896 6.83952 13.0896 6.49238 12.7316C6.14525 12.3736 6.51344 11.7904 6.86057 11.4324L8.33333 9.91374L3.88889 9.91667C3.39797 9.91667 3 9.50629 3 9.00007C3 8.49385 3.39797 8.08347 3.88889 8.08347L8.33333 8.08054L6.86057 6.56187C6.51344 6.20392 6.14525 5.62649 6.49238 5.26853C6.83952 4.91058 7.40233 4.91058 7.74946 5.26853Z"
|
||||
fill={color}
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default IconEditorOnly
|
|
@ -0,0 +1,25 @@
|
|||
function IconPdfOnly() {
|
||||
const color = '#505050' // match color from .dropdown-menu > li > a
|
||||
|
||||
return (
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect x="0.5" y="1.5" width="15" height="14" rx="1.5" stroke={color} />
|
||||
<line x1="1" y1="2.5" x2="15" y2="2.5" stroke={color} />
|
||||
<line x1="2" y1="3" x2="2" y2="15" stroke={color} strokeWidth="2" />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M8.25054 12.7315L5.26035 9.64806C4.91322 9.29011 4.91322 8.70975 5.26035 8.3518L8.25054 5.2684C8.59767 4.91044 9.16048 4.91044 9.50762 5.2684C9.85475 5.62635 9.48656 6.20964 9.13943 6.56759L7.66667 8.08626L12.1111 8.08333C12.602 8.08333 13 8.49371 13 8.99993C13 9.50615 12.602 9.91653 12.1111 9.91653L7.66667 9.91946L9.13943 11.4381C9.48656 11.7961 9.85475 12.3735 9.50762 12.7315C9.16048 13.0894 8.59767 13.0894 8.25054 12.7315Z"
|
||||
fill={color}
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default IconPdfOnly
|
|
@ -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 <IconChecked />
|
||||
} else if (iconFor === 'pdfOnly' && pdfLayout === 'flat' && view === 'pdf') {
|
||||
return <IconChecked />
|
||||
} else if (iconFor === 'sideBySide' && pdfLayout === 'sideBySide') {
|
||||
return <IconChecked />
|
||||
}
|
||||
// return empty icon for placeholder
|
||||
return <Icon type="" modifier="fw" />
|
||||
}
|
||||
|
||||
function LayoutDropdownButton({ handleChangeLayout, pdfLayout, view }) {
|
||||
const { t } = useTranslation()
|
||||
// bsStyle is required for Dropdown.Toggle, but we will override style
|
||||
return (
|
||||
<ControlledDropdown id="layout-dropdown" className="toolbar-item">
|
||||
<Dropdown.Toggle className="btn-full-height" bsStyle="link">
|
||||
<Icon type="columns" modifier="fw" />
|
||||
<span className="toolbar-label">{t('layout')}</span>
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu id="layout-dropdown-list">
|
||||
<MenuItem header>{t('layout')}</MenuItem>
|
||||
<MenuItem onSelect={() => handleChangeLayout('sideBySide')}>
|
||||
<IconCheckmark
|
||||
iconFor="sideBySide"
|
||||
pdfLayout={pdfLayout}
|
||||
view={view}
|
||||
/>
|
||||
<Icon type="columns" />
|
||||
{t('editor_and_pdf')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onSelect={() => handleChangeLayout('flat', 'editor')}
|
||||
className="menu-item-with-svg"
|
||||
>
|
||||
<IconCheckmark
|
||||
iconFor="editorOnly"
|
||||
pdfLayout={pdfLayout}
|
||||
view={view}
|
||||
/>
|
||||
<IconEditorOnly />
|
||||
<Trans
|
||||
i18nKey="editor_only_hide_pdf"
|
||||
components={[
|
||||
<span key="editor_only_hide_pdf" className="subdued" />,
|
||||
]}
|
||||
/>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onSelect={() => handleChangeLayout('flat', 'pdf')}
|
||||
className="menu-item-with-svg"
|
||||
>
|
||||
<IconCheckmark iconFor="pdfOnly" pdfLayout={pdfLayout} view={view} />
|
||||
<IconPdfOnly />
|
||||
<Trans
|
||||
i18nKey="pdf_only_hide_editor"
|
||||
components={[
|
||||
<span key="pdf_only_hide_editor" className="subdued" />,
|
||||
]}
|
||||
/>
|
||||
</MenuItem>
|
||||
</Dropdown.Menu>
|
||||
</ControlledDropdown>
|
||||
)
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
|
@ -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 (
|
||||
<header className="toolbar toolbar-header toolbar-with-labels">
|
||||
<header
|
||||
className="toolbar toolbar-header toolbar-with-labels"
|
||||
role="navigation"
|
||||
aria-label={t('project_layout_sharing_submission')}
|
||||
>
|
||||
<div className="toolbar-left">
|
||||
<MenuButton onClick={onShowLeftMenuClick} />
|
||||
{cobranding && cobranding.logoImgUrl && (
|
||||
|
@ -67,6 +77,14 @@ const ToolbarHeader = React.memo(function ToolbarHeader({
|
|||
<div className="toolbar-right">
|
||||
<OnlineUsersWidget onlineUsers={onlineUsers} goToUser={goToUser} />
|
||||
|
||||
{window.showPdfDetach && (
|
||||
<LayoutDropdownButton
|
||||
handleChangeLayout={handleChangeLayout}
|
||||
pdfLayout={pdfLayout}
|
||||
view={view}
|
||||
/>
|
||||
)}
|
||||
|
||||
{shouldDisplayTrackChangesButton && (
|
||||
<TrackChangesToggleButton
|
||||
onClick={toggleReviewPanelOpen}
|
||||
|
@ -98,6 +116,7 @@ const ToolbarHeader = React.memo(function ToolbarHeader({
|
|||
|
||||
ToolbarHeader.propTypes = {
|
||||
onShowLeftMenuClick: PropTypes.func.isRequired,
|
||||
handleChangeLayout: PropTypes.func.isRequired,
|
||||
cobranding: PropTypes.object,
|
||||
chatIsOpen: PropTypes.bool,
|
||||
toggleChatOpen: PropTypes.func.isRequired,
|
||||
|
@ -114,10 +133,12 @@ ToolbarHeader.propTypes = {
|
|||
renameProject: PropTypes.func.isRequired,
|
||||
hasRenamePermissions: PropTypes.bool,
|
||||
openShareModal: PropTypes.func.isRequired,
|
||||
pdfLayout: PropTypes.string.isRequired,
|
||||
pdfViewIsOpen: PropTypes.bool,
|
||||
pdfButtonIsVisible: PropTypes.bool,
|
||||
togglePdfView: PropTypes.func.isRequired,
|
||||
trackChangesVisible: PropTypes.bool,
|
||||
view: PropTypes.string,
|
||||
}
|
||||
|
||||
export default ToolbarHeader
|
||||
|
|
10
services/web/frontend/js/shared/components/icon-checked.js
Normal file
10
services/web/frontend/js/shared/components/icon-checked.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import Icon from './icon'
|
||||
|
||||
function IconChecked() {
|
||||
const { t } = useTranslation()
|
||||
return <Icon type="check" modifier="fw" accessibilityLabel={t('selected')} />
|
||||
}
|
||||
|
||||
export default IconChecked
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)</0>",
|
||||
"pdf_only_hide_editor": "PDF only <0>(hide editor)</0>",
|
||||
"selected": "Selected",
|
||||
"project_layout_sharing_submission": "Project Layout, Sharing, and Submission"
|
||||
}
|
||||
|
|
|
@ -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('<LayoutDropdownButton />', 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(<LayoutDropdownButton {...defaultProps} />)
|
||||
screen.getByRole('menuitem', {
|
||||
name: 'Editor & PDF',
|
||||
})
|
||||
screen.getByRole('menuitem', {
|
||||
name: 'PDF only (hide editor)',
|
||||
})
|
||||
screen.getByRole('menuitem', {
|
||||
name: 'Selected Editor only (hide PDF)',
|
||||
})
|
||||
})
|
||||
})
|
Loading…
Reference in a new issue