From 975d1ee25053e9ade679a97fe8a97b6de49b758f Mon Sep 17 00:00:00 2001 From: David <33458145+davidmcpowell@users.noreply.github.com> Date: Wed, 20 Aug 2025 11:25:48 +0100 Subject: [PATCH] Merge pull request #27316 from overleaf/dp-file-menu-options Add copy and submit project options to new editor file menu GitOrigin-RevId: 7f402d96f278f2b084375441089b286adaa731b8 --- .../editor-clone-project-modal-wrapper.tsx | 16 +++++++++--- .../components/actions-copy-project.tsx | 15 ++--------- .../components/toolbar/duplicate-project.tsx | 15 ++--------- .../components/toolbar/menu-bar.tsx | 26 +++++++++++++++++-- .../js/shared/hooks/use-open-project.ts | 15 +++++++++++ 5 files changed, 56 insertions(+), 31 deletions(-) create mode 100644 services/web/frontend/js/shared/hooks/use-open-project.ts diff --git a/services/web/frontend/js/features/clone-project-modal/components/editor-clone-project-modal-wrapper.tsx b/services/web/frontend/js/features/clone-project-modal/components/editor-clone-project-modal-wrapper.tsx index cae44dc1e0..8efc1a696b 100644 --- a/services/web/frontend/js/features/clone-project-modal/components/editor-clone-project-modal-wrapper.tsx +++ b/services/web/frontend/js/features/clone-project-modal/components/editor-clone-project-modal-wrapper.tsx @@ -1,8 +1,12 @@ -import React from 'react' +import React, { useCallback } from 'react' import { useProjectContext } from '../../../shared/context/project-context' import withErrorBoundary from '../../../infrastructure/error-boundary' import CloneProjectModal from './clone-project-modal' +type ProjectCopyResponse = { + project_id: string +} + const EditorCloneProjectModalWrapper = React.memo( function EditorCloneProjectModalWrapper({ show, @@ -11,9 +15,15 @@ const EditorCloneProjectModalWrapper = React.memo( }: { show: boolean handleHide: () => void - openProject: ({ project_id }: { project_id: string }) => void + openProject: (projectId: string) => void }) { const { project, tags: projectTags } = useProjectContext() + const handleAfterCloned = useCallback( + ({ project_id: projectId }: ProjectCopyResponse) => { + openProject(projectId) + }, + [openProject] + ) if (!project) { // wait for useProjectContext @@ -23,7 +33,7 @@ const EditorCloneProjectModalWrapper = React.memo( { - location.assign(`/project/${projectId}`) - }, - [location] - ) + const openProject = useOpenProject() const handleShowModal = useCallback(() => { eventTracking.sendMB('left-menu-copy') diff --git a/services/web/frontend/js/features/ide-redesign/components/toolbar/duplicate-project.tsx b/services/web/frontend/js/features/ide-redesign/components/toolbar/duplicate-project.tsx index 2c7720f2d0..711420cc39 100644 --- a/services/web/frontend/js/features/ide-redesign/components/toolbar/duplicate-project.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/toolbar/duplicate-project.tsx @@ -1,28 +1,17 @@ import EditorCloneProjectModalWrapper from '@/features/clone-project-modal/components/editor-clone-project-modal-wrapper' import OLDropdownMenuItem from '@/shared/components/ol/ol-dropdown-menu-item' import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics' -import { useLocation } from '@/shared/hooks/use-location' +import useOpenProject from '@/shared/hooks/use-open-project' import getMeta from '@/utils/meta' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' -type ProjectCopyResponse = { - project_id: string -} - export const DuplicateProject = () => { const { sendEvent } = useEditorAnalytics() const { t } = useTranslation() const [showModal, setShowModal] = useState(false) - const location = useLocation() const anonymous = getMeta('ol-anonymous') - - const openProject = useCallback( - ({ project_id: projectId }: ProjectCopyResponse) => { - location.assign(`/project/${projectId}`) - }, - [location] - ) + const openProject = useOpenProject() const handleShowModal = useCallback(() => { sendEvent('copy-project', { location: 'project-title-dropdown' }) diff --git a/services/web/frontend/js/features/ide-redesign/components/toolbar/menu-bar.tsx b/services/web/frontend/js/features/ide-redesign/components/toolbar/menu-bar.tsx index 67279da060..39ffdc3485 100644 --- a/services/web/frontend/js/features/ide-redesign/components/toolbar/menu-bar.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/toolbar/menu-bar.tsx @@ -27,6 +27,9 @@ import { useDetachCompileContext as useCompileContext } from '@/shared/context/d import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics' import { useProjectSettingsContext } from '@/features/editor-left-menu/context/project-settings-context' import { useSurveyUrl } from '../../hooks/use-survey-url' +import getMeta from '@/utils/meta' +import EditorCloneProjectModalWrapper from '@/features/clone-project-modal/components/editor-clone-project-modal-wrapper' +import useOpenProject from '@/shared/hooks/use-open-project' export const ToolbarMenuBar = () => { const { t } = useTranslation() @@ -38,6 +41,10 @@ export const ToolbarMenuBar = () => { const { pdfUrl } = useCompileContext() const wordCountEnabled = pdfUrl || isSplitTestEnabled('word-count-client') const [showWordCountModal, setShowWordCountModal] = useState(false) + const [showCloneProjectModal, setShowCloneProjectModal] = useState(false) + const openProject = useOpenProject() + + const anonymous = getMeta('ol-anonymous') useCommandProvider( () => [ @@ -58,16 +65,26 @@ export const ToolbarMenuBar = () => { }, id: 'word_count', }, + { + type: 'command', + label: t('make_a_copy'), + disabled: anonymous, + handler: () => { + setShowCloneProjectModal(true) + }, + id: 'copy_project', + }, ], - [t, setView, view, wordCountEnabled] + [t, setView, view, wordCountEnabled, anonymous] ) const fileMenuStructure: MenuStructure = useMemo( () => [ { id: 'file-file-tree', - children: ['new_file', 'new_folder', 'upload_file'], + children: ['new_file', 'new_folder', 'upload_file', 'copy_project'], }, { id: 'file-tools', children: ['show_version_history', 'word_count'] }, + { id: 'submit', children: ['submit-project'] }, { id: 'file-download', children: ['download-as-source-zip', 'download-pdf'], @@ -286,6 +303,11 @@ export const ToolbarMenuBar = () => { show={showWordCountModal} handleHide={() => setShowWordCountModal(false)} /> + setShowCloneProjectModal(false)} + openProject={openProject} + /> ) } diff --git a/services/web/frontend/js/shared/hooks/use-open-project.ts b/services/web/frontend/js/shared/hooks/use-open-project.ts new file mode 100644 index 0000000000..f6deefafd1 --- /dev/null +++ b/services/web/frontend/js/shared/hooks/use-open-project.ts @@ -0,0 +1,15 @@ +import { useCallback } from 'react' +import { useLocation } from './use-location' + +export default function useOpenProject() { + const location = useLocation() + + const openProject = useCallback( + (projectId: string) => { + location.assign(`/project/${projectId}`) + }, + [location] + ) + + return openProject +}