Просмотр исходного кода

Merge pull request #25752 from overleaf/dp-delete-multiple-files

Add bulk delete button to file tree toolbar

GitOrigin-RevId: c857d8f5027eddb29b1ca324efe1a0e94ef4c28b
David 1 год назад
Родитель
Сommit
061f10a059

BIN
services/web/frontend/fonts/material-symbols/MaterialSymbolsRoundedUnfilledPartialSlice.woff2


+ 1 - 0
services/web/frontend/fonts/material-symbols/unfilled-symbols.mjs

@@ -8,6 +8,7 @@ export default /** @type {const} */ ([
   'brush',
   'code',
   'create_new_folder',
+  'delete',
   'description',
   'experiment',
   'forum',

+ 1 - 1
services/web/frontend/js/features/file-tree/components/file-tree-root.tsx

@@ -6,7 +6,7 @@ import FileTreeContext from './file-tree-context'
 import FileTreeDraggablePreviewLayer from './file-tree-draggable-preview-layer'
 import FileTreeFolderList from './file-tree-folder-list'
 import FileTreeToolbar from './file-tree-toolbar'
-import FileTreeToolbarNew from '@/features/ide-redesign/components/file-tree-toolbar'
+import FileTreeToolbarNew from '@/features/ide-redesign/components/file-tree/file-tree-toolbar'
 import FileTreeModalDelete from './modals/file-tree-modal-delete'
 import FileTreeModalCreateFolder from './modals/file-tree-modal-create-folder'
 import FileTreeModalError from './modals/file-tree-modal-error'

+ 4 - 1
services/web/frontend/js/features/file-tree/contexts/file-tree-actionable.tsx

@@ -57,6 +57,7 @@ const FileTreeActionableContext = createContext<
       newFileCreateMode: any | null
       error: any | null
       canDelete: boolean
+      canBulkDelete: boolean
       canRename: boolean
       canCreate: boolean
       parentFolderId: string
@@ -509,6 +510,8 @@ export const FileTreeActionableProvider: FC<React.PropsWithChildren> = ({
   const value = useMemo(
     () => ({
       canDelete: write && selectedEntityIds.size > 0 && !isRootFolderSelected,
+      canBulkDelete:
+        write && selectedEntityIds.size > 1 && !isRootFolderSelected,
       canRename: write && selectedEntityIds.size === 1 && !isRootFolderSelected,
       canCreate: write && selectedEntityIds.size < 2,
       ...state,
@@ -545,8 +548,8 @@ export const FileTreeActionableProvider: FC<React.PropsWithChildren> = ({
       isDuplicate,
       isRootFolderSelected,
       parentFolderId,
-      selectedEntityIds.size,
       selectedFileName,
+      selectedEntityIds.size,
       startCreatingDocOrFile,
       startCreatingFile,
       startCreatingFolder,

+ 0 - 149
services/web/frontend/js/features/ide-redesign/components/file-tree-toolbar.tsx

@@ -1,149 +0,0 @@
-import { useTranslation } from 'react-i18next'
-import * as eventTracking from '../../../infrastructure/event-tracking'
-import { useFileTreeActionable } from '@/features/file-tree/contexts/file-tree-actionable'
-import { useFileTreeData } from '@/shared/context/file-tree-data-context'
-import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
-import MaterialIcon, {
-  AvailableUnfilledIcon,
-} from '@/shared/components/material-icon'
-import React from 'react'
-import useCollapsibleFileTree from '../hooks/use-collapsible-file-tree'
-import { useCommandProvider } from '@/features/ide-react/hooks/use-command-provider'
-import { usePermissionsContext } from '@/features/ide-react/context/permissions-context'
-
-function FileTreeToolbar() {
-  const { t } = useTranslation()
-  const { fileTreeExpanded, toggleFileTreeExpanded } = useCollapsibleFileTree()
-
-  return (
-    <div className="file-tree-toolbar">
-      <button
-        className="file-tree-expand-collapse-button"
-        onClick={toggleFileTreeExpanded}
-        aria-label={
-          fileTreeExpanded ? t('hide_file_tree') : t('show_file_tree')
-        }
-      >
-        <MaterialIcon
-          type={
-            fileTreeExpanded ? 'keyboard_arrow_down' : 'keyboard_arrow_right'
-          }
-        />
-        <h4>{t('file_tree')}</h4>
-      </button>
-      <FileTreeActionButtons />
-    </div>
-  )
-}
-
-function FileTreeActionButtons() {
-  const { t } = useTranslation()
-  const { fileTreeReadOnly } = useFileTreeData()
-  const { write } = usePermissionsContext()
-
-  const {
-    canCreate,
-    startCreatingFolder,
-    startCreatingDocOrFile,
-    startUploadingDocOrFile,
-  } = useFileTreeActionable()
-  useCommandProvider(() => {
-    if (!canCreate || fileTreeReadOnly || !write) return
-    return [
-      {
-        label: t('new_file'),
-        id: 'new_file',
-        handler: ({ location }) => {
-          eventTracking.sendMB('new-file-click', { location })
-          startCreatingDocOrFile()
-        },
-      },
-      {
-        label: t('new_folder'),
-        id: 'new_folder',
-        handler: startCreatingFolder,
-      },
-      {
-        label: t('upload_file'),
-        id: 'upload_file',
-        handler: ({ location }) => {
-          eventTracking.sendMB('upload-click', { location })
-          startUploadingDocOrFile()
-        },
-      },
-    ]
-  }, [
-    canCreate,
-    fileTreeReadOnly,
-    startCreatingDocOrFile,
-    t,
-    startCreatingFolder,
-    startUploadingDocOrFile,
-    write,
-  ])
-
-  if (!canCreate || fileTreeReadOnly) return null
-
-  const createWithAnalytics = () => {
-    eventTracking.sendMB('new-file-click', { location: 'toolbar' })
-    startCreatingDocOrFile()
-  }
-
-  const uploadWithAnalytics = () => {
-    eventTracking.sendMB('upload-click', { location: 'toolbar' })
-    startUploadingDocOrFile()
-  }
-
-  return (
-    <div className="file-tree-toolbar-action-buttons">
-      <FileTreeActionButton
-        id="new-file"
-        description={t('new_file')}
-        onClick={createWithAnalytics}
-        iconType="note_add"
-      />
-      <FileTreeActionButton
-        id="new-folder"
-        description={t('new_folder')}
-        onClick={startCreatingFolder}
-        iconType="create_new_folder"
-      />
-      <FileTreeActionButton
-        id="upload"
-        description={t('upload')}
-        onClick={uploadWithAnalytics}
-        iconType="upload_file"
-      />
-    </div>
-  )
-}
-
-function FileTreeActionButton({
-  id,
-  description,
-  onClick,
-  iconType,
-}: {
-  id: string
-  description: string
-  onClick: () => void
-  iconType: AvailableUnfilledIcon
-}) {
-  return (
-    <OLTooltip
-      id={id}
-      description={description}
-      overlayProps={{ placement: 'bottom' }}
-    >
-      <button className="btn file-tree-toolbar-action-button" onClick={onClick}>
-        <MaterialIcon
-          unfilled
-          type={iconType}
-          accessibilityLabel={description}
-        />
-      </button>
-    </OLTooltip>
-  )
-}
-
-export default FileTreeToolbar

+ 33 - 0
services/web/frontend/js/features/ide-redesign/components/file-tree/file-tree-action-button.tsx

@@ -0,0 +1,33 @@
+import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
+import MaterialIcon, {
+  AvailableUnfilledIcon,
+} from '@/shared/components/material-icon'
+import React from 'react'
+
+export default function FileTreeActionButton({
+  id,
+  description,
+  onClick,
+  iconType,
+}: {
+  id: string
+  description: string
+  onClick: () => void
+  iconType: AvailableUnfilledIcon
+}) {
+  return (
+    <OLTooltip
+      id={id}
+      description={description}
+      overlayProps={{ placement: 'bottom' }}
+    >
+      <button className="btn file-tree-toolbar-action-button" onClick={onClick}>
+        <MaterialIcon
+          unfilled
+          type={iconType}
+          accessibilityLabel={description}
+        />
+      </button>
+    </OLTooltip>
+  )
+}

+ 107 - 0
services/web/frontend/js/features/ide-redesign/components/file-tree/file-tree-action-buttons.tsx

@@ -0,0 +1,107 @@
+import { useTranslation } from 'react-i18next'
+import * as eventTracking from '../../../../infrastructure/event-tracking'
+import { useFileTreeActionable } from '@/features/file-tree/contexts/file-tree-actionable'
+import { useFileTreeData } from '@/shared/context/file-tree-data-context'
+import React from 'react'
+import { useCommandProvider } from '@/features/ide-react/hooks/use-command-provider'
+import { usePermissionsContext } from '@/features/ide-react/context/permissions-context'
+import FileTreeActionButton from './file-tree-action-button'
+
+export default function FileTreeActionButtons() {
+  const { t } = useTranslation()
+  const { fileTreeReadOnly } = useFileTreeData()
+  const { write } = usePermissionsContext()
+
+  const {
+    canCreate,
+    canBulkDelete,
+    startDeleting,
+    startCreatingFolder,
+    startCreatingDocOrFile,
+    startUploadingDocOrFile,
+  } = useFileTreeActionable()
+
+  useCommandProvider(() => {
+    if (!canCreate || fileTreeReadOnly || !write) return
+    return [
+      {
+        label: t('new_file'),
+        id: 'new_file',
+        handler: ({ location }) => {
+          eventTracking.sendMB('new-file-click', { location })
+          startCreatingDocOrFile()
+        },
+      },
+      {
+        label: t('new_folder'),
+        id: 'new_folder',
+        handler: startCreatingFolder,
+      },
+      {
+        label: t('upload_file'),
+        id: 'upload_file',
+        handler: ({ location }) => {
+          eventTracking.sendMB('upload-click', { location })
+          startUploadingDocOrFile()
+        },
+      },
+    ]
+  }, [
+    canCreate,
+    fileTreeReadOnly,
+    startCreatingDocOrFile,
+    t,
+    startCreatingFolder,
+    startUploadingDocOrFile,
+    write,
+  ])
+
+  if (fileTreeReadOnly) return null
+
+  const createWithAnalytics = () => {
+    eventTracking.sendMB('new-file-click', { location: 'toolbar' })
+    startCreatingDocOrFile()
+  }
+
+  const uploadWithAnalytics = () => {
+    eventTracking.sendMB('upload-click', { location: 'toolbar' })
+    startUploadingDocOrFile()
+  }
+
+  return (
+    <div className="file-tree-toolbar-action-buttons">
+      {canCreate && (
+        <FileTreeActionButton
+          id="new-file"
+          description={t('new_file')}
+          onClick={createWithAnalytics}
+          iconType="note_add"
+        />
+      )}
+      {canCreate && (
+        <FileTreeActionButton
+          id="new-folder"
+          description={t('new_folder')}
+          onClick={startCreatingFolder}
+          iconType="create_new_folder"
+        />
+      )}
+      {canCreate && (
+        <FileTreeActionButton
+          id="upload"
+          description={t('upload')}
+          onClick={uploadWithAnalytics}
+          iconType="upload_file"
+        />
+      )}
+      {canBulkDelete && (
+        <FileTreeActionButton
+          id="delete"
+          description={t('delete')}
+          onClick={startDeleting}
+          iconType="delete"
+        />
+      )}
+    </div>
+  )
+}

+ 1 - 1
services/web/frontend/js/features/ide-redesign/components/file-tree-outline-panel.tsx → services/web/frontend/js/features/ide-redesign/components/file-tree/file-tree-outline-panel.tsx

@@ -3,7 +3,7 @@ import { FileTree } from '@/features/ide-react/components/file-tree'
 import { OutlineContainer } from '@/features/outline/components/outline-container'
 import { VerticalResizeHandle } from '@/features/ide-react/components/resize/vertical-resize-handle'
 import { useOutlinePane } from '@/features/ide-react/hooks/use-outline-pane'
-import useCollapsibleFileTree from '../hooks/use-collapsible-file-tree'
+import useCollapsibleFileTree from '../../hooks/use-collapsible-file-tree'
 import classNames from 'classnames'
 
 function FileTreeOutlinePanel() {

+ 32 - 0
services/web/frontend/js/features/ide-redesign/components/file-tree/file-tree-toolbar.tsx

@@ -0,0 +1,32 @@
+import { useTranslation } from 'react-i18next'
+import MaterialIcon from '@/shared/components/material-icon'
+import React from 'react'
+import useCollapsibleFileTree from '../../hooks/use-collapsible-file-tree'
+import FileTreeActionButtons from './file-tree-action-buttons'
+
+function FileTreeToolbar() {
+  const { t } = useTranslation()
+  const { fileTreeExpanded, toggleFileTreeExpanded } = useCollapsibleFileTree()
+
+  return (
+    <div className="file-tree-toolbar">
+      <button
+        className="file-tree-expand-collapse-button"
+        onClick={toggleFileTreeExpanded}
+        aria-label={
+          fileTreeExpanded ? t('hide_file_tree') : t('show_file_tree')
+        }
+      >
+        <MaterialIcon
+          type={
+            fileTreeExpanded ? 'keyboard_arrow_down' : 'keyboard_arrow_right'
+          }
+        />
+        <h4>{t('file_tree')}</h4>
+      </button>
+      <FileTreeActionButtons />
+    </div>
+  )
+}
+
+export default FileTreeToolbar

+ 1 - 1
services/web/frontend/js/features/ide-redesign/components/rail.tsx

@@ -12,7 +12,7 @@ import {
   RailTabKey,
   useRailContext,
 } from '../contexts/rail-context'
-import FileTreeOutlinePanel from './file-tree-outline-panel'
+import FileTreeOutlinePanel from './file-tree/file-tree-outline-panel'
 import { ChatIndicator, ChatPane } from './chat/chat'
 import getMeta from '@/utils/meta'
 import { HorizontalResizeHandle } from '@/features/ide-react/components/resize/horizontal-resize-handle'