Преглед изворни кода

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

GitOrigin-RevId: 04f4abdc529a0494c70b0e3d14847b0cf452b80d
Mathias Jakobsen пре 1 година
родитељ
комит
5a67353dc3

+ 2 - 0
services/web/frontend/extracted-translations.json

@@ -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": "",

+ 69 - 0
services/web/frontend/js/features/ide-redesign/components/toolbar/download-project.tsx

@@ -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
+  }
+}

+ 70 - 0
services/web/frontend/js/features/ide-redesign/components/toolbar/editable-label.tsx

@@ -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

+ 57 - 8
services/web/frontend/js/features/ide-redesign/components/toolbar/project-title.tsx

@@ -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>

+ 0 - 12
services/web/frontend/js/features/ide-redesign/components/toolbar/toolbar.tsx

@@ -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>
   )

+ 7 - 0
services/web/frontend/stylesheets/bootstrap-5/pages/editor/toolbar-redesign.scss

@@ -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;
+}

+ 3 - 0
services/web/locales/en.json

@@ -554,6 +554,8 @@
   "dont_reload_or_close_this_tab": "Don’t 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",