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

Merge pull request #24033 from overleaf/mj-rail-dropdown

[web] Editor redesign: Add help dropdown to rail

GitOrigin-RevId: 052ba1852a8e2702fe1671c4613d986b9b0c91c2
David 1 год назад
Родитель
Сommit
9824151e62

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

@@ -847,6 +847,7 @@
   "keep_personal_projects_separate": "",
   "keep_your_account_safe_add_another_email": "",
   "keybindings": "",
+  "keyboard_shortcuts": "",
   "knowledge_base": "",
   "labels_help_you_to_easily_reference_your_figures": "",
   "labels_help_you_to_reference_your_tables": "",

+ 16 - 0
services/web/frontend/js/features/ide-redesign/components/help/contact-us.tsx

@@ -0,0 +1,16 @@
+import { FC, useCallback } from 'react'
+import ContactUsModal from '../../../../../../modules/support/frontend/js/components/contact-us-modal'
+import { useRailContext } from '../../contexts/rail-context'
+import getMeta from '@/utils/meta'
+
+export const RailHelpContactUsModal: FC<{ show: boolean }> = ({ show }) => {
+  const { setActiveModal } = useRailContext()
+  const handleHide = useCallback(() => setActiveModal(null), [setActiveModal])
+  const showSupport = getMeta('ol-showSupport')
+  if (!showSupport) {
+    return null
+  }
+  return (
+    <ContactUsModal show={show} handleHide={handleHide} autofillProjectUrl />
+  )
+}

+ 19 - 0
services/web/frontend/js/features/ide-redesign/components/help/keyboard-shortcuts.tsx

@@ -0,0 +1,19 @@
+import { FC } from 'react'
+import { useProjectContext } from '@/shared/context/project-context'
+import HotkeysModal from '@/features/hotkeys-modal/components/hotkeys-modal'
+import { isMac } from '@/shared/utils/os'
+import { useRailContext } from '../../contexts/rail-context'
+
+export const RailHelpShowHotkeysModal: FC<{ show: boolean }> = ({ show }) => {
+  const { features } = useProjectContext()
+  const { setActiveModal } = useRailContext()
+
+  return (
+    <HotkeysModal
+      show={show}
+      handleHide={() => setActiveModal(null)}
+      isMac={isMac}
+      trackChangesVisible={features?.trackChangesVisible}
+    />
+  )
+}

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

@@ -6,7 +6,11 @@ import MaterialIcon, {
 import { Panel } from 'react-resizable-panels'
 import { useLayoutContext } from '@/shared/context/layout-context'
 import { ErrorIndicator, ErrorPane } from './errors'
-import { RailTabKey, useRailContext } from '../contexts/rail-context'
+import {
+  RailModalKey,
+  RailTabKey,
+  useRailContext,
+} from '../contexts/rail-context'
 import FileTreeOutlinePanel from './file-tree-outline-panel'
 import { ChatIndicator, ChatPane } from './chat/chat'
 import getMeta from '@/utils/meta'
@@ -16,6 +20,15 @@ import { useTranslation } from 'react-i18next'
 import classNames from 'classnames'
 import IntegrationsPanel from './integrations-panel/integrations-panel'
 import OLButton from '@/features/ui/components/ol/ol-button'
+import {
+  Dropdown,
+  DropdownDivider,
+  DropdownItem,
+  DropdownMenu,
+  DropdownToggle,
+} from '@/features/ui/components/bootstrap-5/dropdown-menu'
+import { RailHelpShowHotkeysModal } from './help/keyboard-shortcuts'
+import { RailHelpContactUsModal } from './help/contact-us'
 
 type RailElement = {
   icon: AvailableUnfilledIcon
@@ -25,13 +38,17 @@ type RailElement = {
   hide?: boolean
 }
 
-type RailActionLink = { key: string; icon: AvailableUnfilledIcon; href: string }
 type RailActionButton = {
   key: string
   icon: AvailableUnfilledIcon
   action: () => void
 }
-type RailAction = RailActionLink | RailActionButton
+type RailDropdown = {
+  key: string
+  icon: AvailableUnfilledIcon
+  dropdown: ReactElement
+}
+type RailAction = RailDropdown | RailActionButton
 
 const RAIL_TABS: RailElement[] = [
   {
@@ -64,9 +81,24 @@ const RAIL_TABS: RailElement[] = [
   },
 ]
 
+const RAIL_MODALS: {
+  key: RailModalKey
+  modalComponentFunction: FC<{ show: boolean }>
+}[] = [
+  {
+    key: 'keyboard-shortcuts',
+    modalComponentFunction: RailHelpShowHotkeysModal,
+  },
+  {
+    key: 'contact-us',
+    modalComponentFunction: RailHelpContactUsModal,
+  },
+]
+
 export const RailLayout = () => {
   const { t } = useTranslation()
   const {
+    activeModal,
     selectedTab,
     setSelectedTab,
     isOpen,
@@ -77,11 +109,14 @@ export const RailLayout = () => {
     togglePane,
     setResizing,
   } = useRailContext()
-
   const { setLeftMenuShown } = useLayoutContext()
   const railActions: RailAction[] = useMemo(
     () => [
-      { key: 'support', icon: 'help', href: '/learn' },
+      {
+        key: 'support',
+        icon: 'help',
+        dropdown: <RailHelpDropdown />,
+      },
       {
         key: 'settings',
         icon: 'settings',
@@ -96,6 +131,12 @@ export const RailLayout = () => {
       if (key === selectedTab) {
         togglePane()
       } else {
+        // HACK: Apparently the onSelect event is triggered with href attributes
+        // from DropdownItems
+        if (!RAIL_TABS.some(tab => !tab.hide && tab.key === key)) {
+          // Attempting to open a non-existent tab
+          return
+        }
         // Change the selected tab and make sure it's open
         setSelectedTab((key ?? 'file-tree') as RailTabKey)
         setIsOpen(true)
@@ -168,6 +209,9 @@ export const RailLayout = () => {
           tooltipWhenClosed={t('tooltip_show_panel')}
         />
       </HorizontalResizeHandle>
+      {RAIL_MODALS.map(({ key, modalComponentFunction: Component }) => (
+        <Component key={key} show={activeModal === key} />
+      ))}
     </TabContainer>
   )
 }
@@ -214,16 +258,18 @@ const RailActionElement = ({ action }: { action: RailAction }) => {
     }
   }, [action])
 
-  if ('href' in action) {
+  if ('dropdown' in action) {
     return (
-      <a
-        href={action.href}
-        target="_blank"
-        rel="noopener"
-        className="ide-rail-tab-link"
-      >
-        {icon}
-      </a>
+      <Dropdown align="end" drop="end">
+        <DropdownToggle
+          id="rail-help-dropdown-btn"
+          className="ide-rail-tab-link ide-rail-tab-button ide-rail-tab-dropdown"
+          as="button"
+        >
+          {icon}
+        </DropdownToggle>
+        {action.dropdown}
+      </Dropdown>
     )
   } else {
     return (
@@ -253,3 +299,44 @@ export const RailPanelHeader: FC<{ title: string }> = ({ title }) => {
     </header>
   )
 }
+
+const RailHelpDropdown = () => {
+  const showSupport = getMeta('ol-showSupport')
+  const { t } = useTranslation()
+  const { setActiveModal } = useRailContext()
+  const openKeyboardShortcutsModal = useCallback(() => {
+    setActiveModal('keyboard-shortcuts')
+  }, [setActiveModal])
+  const openContactUsModal = useCallback(() => {
+    setActiveModal('contact-us')
+  }, [setActiveModal])
+  return (
+    <DropdownMenu>
+      <DropdownItem onClick={openKeyboardShortcutsModal}>
+        {t('keyboard_shortcuts')}
+      </DropdownItem>
+      <DropdownItem
+        href="/learn"
+        role="menuitem"
+        target="_blank"
+        rel="noopener noreferrer"
+      >
+        {t('documentation')}
+      </DropdownItem>
+      <DropdownDivider />
+      {showSupport && (
+        <DropdownItem onClick={openContactUsModal}>
+          {t('contact_us')}
+        </DropdownItem>
+      )}
+      <DropdownItem
+        href="https://forms.gle/soyVStc5qDx9na1Z6"
+        role="menuitem"
+        target="_blank"
+        rel="noopener noreferrer"
+      >
+        {t('give_feedback')}
+      </DropdownItem>
+    </DropdownMenu>
+  )
+}

+ 16 - 0
services/web/frontend/js/features/ide-redesign/contexts/rail-context.tsx

@@ -19,6 +19,8 @@ export type RailTabKey =
   | 'chat'
   | 'errors'
 
+export type RailModalKey = 'keyboard-shortcuts' | 'contact-us'
+
 const RailContext = createContext<
   | {
       selectedTab: RailTabKey
@@ -31,6 +33,8 @@ const RailContext = createContext<
       handlePaneCollapse: () => void
       resizing: boolean
       setResizing: Dispatch<SetStateAction<boolean>>
+      activeModal: RailModalKey | null
+      setActiveModal: Dispatch<SetStateAction<RailModalKey | null>>
     }
   | undefined
 >(undefined)
@@ -38,6 +42,14 @@ const RailContext = createContext<
 export const RailProvider: FC = ({ children }) => {
   const [isOpen, setIsOpen] = useState(true)
   const [resizing, setResizing] = useState(false)
+  const [activeModal, setActiveModalInternal] = useState<RailModalKey | null>(
+    null
+  )
+  const setActiveModal: Dispatch<SetStateAction<RailModalKey | null>> =
+    useCallback(modalKey => {
+      setActiveModalInternal(modalKey)
+    }, [])
+
   const panelRef = useRef<ImperativePanelHandle>(null)
   useCollapsiblePanel(isOpen, panelRef)
 
@@ -69,6 +81,8 @@ export const RailProvider: FC = ({ children }) => {
       handlePaneCollapse,
       resizing,
       setResizing,
+      activeModal,
+      setActiveModal,
     }),
     [
       selectedTab,
@@ -81,6 +95,8 @@ export const RailProvider: FC = ({ children }) => {
       handlePaneCollapse,
       resizing,
       setResizing,
+      activeModal,
+      setActiveModal,
     ]
   )
 

+ 8 - 0
services/web/frontend/stylesheets/bootstrap-5/pages/editor/rail.scss

@@ -133,3 +133,11 @@
   gap: var(--spacing-02);
   width: 40px;
 }
+
+.ide-rail-tab-dropdown {
+  border: 0;
+
+  &.dropdown-toggle::after {
+    display: none;
+  }
+}

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

@@ -1110,6 +1110,7 @@
   "keep_your_account_safe_add_another_email": "Keep your account safe and make sure you don’t lose access to it by adding another email address.",
   "keep_your_email_updated": "Keep your email updated so that you don’t lose access to your account and data.",
   "keybindings": "Keybindings",
+  "keyboard_shortcuts": "Keyboard shortcuts",
   "knowledge_base": "knowledge base",
   "ko": "Korean",
   "labels_help_you_to_easily_reference_your_figures": "Labels help you to easily reference your figures throughout your document. To reference a figure within the text, reference the label using the <0>\\ref{...}</0> command. This makes it easy to reference figures without needing to manually remember the figure numbering. <1>Learn more</1>",