[web] Editor redesign: Add actions to project name dropdown (#24220)

GitOrigin-RevId: 04f4abdc529a0494c70b0e3d14847b0cf452b80d
This commit is contained in:
Mathias Jakobsen
2025-03-13 09:46:48 +00:00
committed by Copybot
parent 0a726ff003
commit ae4e5b30c1
7 changed files with 208 additions and 20 deletions

View File

@@ -427,6 +427,8 @@
"dont_reload_or_close_this_tab": "",
"download": "",
"download_all": "",
"download_as_pdf": "",
"download_as_source_zip": "",
"download_metadata": "",
"download_pdf": "",
"download_zip_file": "",

View File

@@ -0,0 +1,69 @@
import OLDropdownMenuItem from '@/features/ui/components/ol/ol-dropdown-menu-item'
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
import { isSmallDevice, sendMB } from '@/infrastructure/event-tracking'
import { useDetachCompileContext as useCompileContext } from '@/shared/context/detach-compile-context'
import { useProjectContext } from '@/shared/context/project-context'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
export const DownloadProjectZip = () => {
const { t } = useTranslation()
const { _id: projectId } = useProjectContext()
const sendDownloadEvent = useCallback(() => {
sendMB('download-zip-button-click', {
projectId,
location: 'project-name-dropdown',
isSmallDevice,
})
}, [projectId])
return (
<OLDropdownMenuItem
href={`/project/${projectId}/download/zip`}
target="_blank"
rel="noreferrer"
onClick={sendDownloadEvent}
>
{t('download_as_source_zip')}
</OLDropdownMenuItem>
)
}
export const DownloadProjectPDF = () => {
const { t } = useTranslation()
const { pdfDownloadUrl, pdfUrl } = useCompileContext()
const { _id: projectId } = useProjectContext()
const sendDownloadEvent = useCallback(() => {
sendMB('download-pdf-button-click', {
projectId,
location: 'project-name-dropdown',
isSmallDevice,
})
}, [projectId])
const button = (
<OLDropdownMenuItem
href={pdfDownloadUrl || pdfUrl}
target="_blank"
rel="noreferrer"
onClick={sendDownloadEvent}
disabled={!pdfUrl}
>
{t('download_as_pdf')}
</OLDropdownMenuItem>
)
if (!pdfUrl) {
return (
<OLTooltip
id="tooltip-download-pdf-unavailable"
description={t('please_compile_pdf_before_download')}
overlayProps={{ placement: 'right', delay: 0 }}
>
<span>{button}</span>
</OLTooltip>
)
} else {
return button
}
}

View File

@@ -0,0 +1,70 @@
import OLFormControl from '@/features/ui/components/ol/ol-form-control'
import {
ChangeEventHandler,
useCallback,
useEffect,
useRef,
useState,
} from 'react'
type EditableLabelProps = {
initialText: string
className?: string
onChange: (name: string) => void
onCancel: () => void
maxLength?: number
}
const EditableLabel = ({
initialText,
className,
onChange,
onCancel,
maxLength,
}: EditableLabelProps) => {
const [name, setName] = useState(initialText)
const inputRef = useRef<HTMLInputElement | null>(null)
useEffect(() => {
inputRef.current?.select()
}, [])
const onInputChange: ChangeEventHandler<HTMLInputElement> = useCallback(
event => {
setName(event.target.value)
},
[]
)
const finishRenaming = useCallback(() => {
onChange(name)
}, [onChange, name])
const onKeyDown = useCallback(
(event: React.KeyboardEvent) => {
if (event.key === 'Enter') {
event.preventDefault()
finishRenaming()
}
if (event.key === 'Escape') {
event.preventDefault()
onCancel()
}
},
[finishRenaming, onCancel]
)
return (
<OLFormControl
className={className}
ref={inputRef}
type="text"
value={name}
onChange={onInputChange}
onKeyDown={onKeyDown}
onBlur={finishRenaming}
maxLength={maxLength}
/>
)
}
export default EditableLabel

View File

@@ -4,14 +4,53 @@ import {
DropdownMenu,
DropdownToggle,
} from '@/features/ui/components/bootstrap-5/dropdown-menu'
import OLDropdownMenuItem from '@/features/ui/components/ol/ol-dropdown-menu-item'
import MaterialIcon from '@/shared/components/material-icon'
import { useProjectContext } from '@/shared/context/project-context'
import { useTranslation } from 'react-i18next'
import importOverleafModules from '../../../../../macros/import-overleaf-module.macro'
import { useEditorContext } from '@/shared/context/editor-context'
import { DownloadProjectPDF, DownloadProjectZip } from './download-project'
import { useCallback, useState } from 'react'
import OLDropdownMenuItem from '@/features/ui/components/ol/ol-dropdown-menu-item'
import EditableLabel from './editable-label'
const [publishModalModules] = importOverleafModules('publishModal')
const SubmitProjectButton = publishModalModules?.import.NewPublishToolbarButton
export const ToolbarProjectTitle = () => {
const { name } = useProjectContext()
const { t } = useTranslation()
const { permissionsLevel, renameProject } = useEditorContext()
const { name } = useProjectContext()
const shouldDisplaySubmitButton =
(permissionsLevel === 'owner' || permissionsLevel === 'readAndWrite') &&
SubmitProjectButton
const hasRenamePermissions = permissionsLevel === 'owner'
const [isRenaming, setIsRenaming] = useState(false)
const onRename = useCallback(
name => {
if (name) {
renameProject(name)
}
setIsRenaming(false)
},
[renameProject]
)
const onCancel = useCallback(() => {
setIsRenaming(false)
}, [])
if (isRenaming) {
return (
<EditableLabel
onChange={onRename}
onCancel={onCancel}
initialText={name}
maxLength={150}
className="ide-redesign-toolbar-editable-project-name"
/>
)
}
return (
<Dropdown align="start">
<DropdownToggle
@@ -24,13 +63,23 @@ export const ToolbarProjectTitle = () => {
accessibilityLabel={t('project_title_options')}
/>
</DropdownToggle>
<DropdownMenu>
<OLDropdownMenuItem>TODO: Export</OLDropdownMenuItem>
<DropdownMenu renderOnMount>
{shouldDisplaySubmitButton && (
<>
<SubmitProjectButton />
<DropdownDivider />
</>
)}
<DownloadProjectPDF />
<DownloadProjectZip />
<DropdownDivider />
<OLDropdownMenuItem>{t('rename')}</OLDropdownMenuItem>
<OLDropdownMenuItem>{t('download')}</OLDropdownMenuItem>
<OLDropdownMenuItem className="text-danger">
{t('delete')}
<OLDropdownMenuItem
onClick={() => {
setIsRenaming(true)
}}
disabled={!hasRenamePermissions}
>
{t('rename')}
</OLDropdownMenuItem>
</DropdownMenu>
</Dropdown>

View File

@@ -4,14 +4,9 @@ import { ToolbarMenuBar } from './menu-bar'
import { ToolbarProjectTitle } from './project-title'
import { OnlineUsers } from './online-users'
import ShareProjectButton from './share-project-button'
import importOverleafModules from '../../../../../macros/import-overleaf-module.macro'
import { useEditorContext } from '@/shared/context/editor-context'
import ChangeLayoutButton from './change-layout-button'
import ShowHistoryButton from './show-history-button'
const [publishModalModules] = importOverleafModules('publishModal')
const SubmitProjectButton = publishModalModules?.import.NewPublishToolbarButton
export const Toolbar = () => {
return (
<div className="ide-redesign-toolbar">
@@ -38,18 +33,11 @@ const ToolbarMenus = () => {
}
const ToolbarButtons = () => {
const { permissionsLevel } = useEditorContext()
const shouldDisplaySubmitButton =
(permissionsLevel === 'owner' || permissionsLevel === 'readAndWrite') &&
SubmitProjectButton
return (
<div className="ide-redesign-toolbar-actions">
<OnlineUsers />
<ShowHistoryButton />
<ChangeLayoutButton />
{shouldDisplaySubmitButton && <SubmitProjectButton />}
<ShareProjectButton />
</div>
)

View File

@@ -142,3 +142,10 @@
.ide-redesign-online-users {
display: flex;
}
.ide-redesign-toolbar-editable-project-name {
text-align: center;
width: 100%;
max-width: 400px;
margin: var(--spacing-02) 0;
}

View File

@@ -554,6 +554,8 @@
"dont_reload_or_close_this_tab": "Dont reload or close this tab.",
"download": "Download",
"download_all": "Download all",
"download_as_pdf": "Download as PDF",
"download_as_source_zip": "Download as source (.zip)",
"download_metadata": "Download Overleaf metadata",
"download_pdf": "Download PDF",
"download_zip_file": "Download .zip file",
@@ -587,6 +589,7 @@
"dropbox_unlinked_premium_feature": "<0>Your Dropbox account has been unlinked</0> because Dropbox Sync is a premium feature that you had through an institutional license.",
"due_date": "Due __date__",
"due_today": "Due today",
"duplicate": "Duplicate",
"duplicate_file": "Duplicate File",
"duplicate_projects": "This user has projects with duplicate names",
"each_user_will_have_access_to": "Each user will have access to",