diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index ad684dd166..1187aedbab 100644 --- a/services/web/frontend/extracted-translations.json +++ b/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": "", diff --git a/services/web/frontend/js/features/ide-redesign/components/help/contact-us.tsx b/services/web/frontend/js/features/ide-redesign/components/help/contact-us.tsx new file mode 100644 index 0000000000..cadb741aef --- /dev/null +++ b/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 ( + + ) +} diff --git a/services/web/frontend/js/features/ide-redesign/components/help/keyboard-shortcuts.tsx b/services/web/frontend/js/features/ide-redesign/components/help/keyboard-shortcuts.tsx new file mode 100644 index 0000000000..dd764c46dc --- /dev/null +++ b/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 ( + setActiveModal(null)} + isMac={isMac} + trackChangesVisible={features?.trackChangesVisible} + /> + ) +} diff --git a/services/web/frontend/js/features/ide-redesign/components/rail.tsx b/services/web/frontend/js/features/ide-redesign/components/rail.tsx index 9fba7b352f..6e00e71738 100644 --- a/services/web/frontend/js/features/ide-redesign/components/rail.tsx +++ b/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: , + }, { 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')} /> + {RAIL_MODALS.map(({ key, modalComponentFunction: Component }) => ( + + ))} ) } @@ -214,16 +258,18 @@ const RailActionElement = ({ action }: { action: RailAction }) => { } }, [action]) - if ('href' in action) { + if ('dropdown' in action) { return ( - - {icon} - + + + {icon} + + {action.dropdown} + ) } else { return ( @@ -253,3 +299,44 @@ export const RailPanelHeader: FC<{ title: string }> = ({ title }) => { ) } + +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 ( + + + {t('keyboard_shortcuts')} + + + {t('documentation')} + + + {showSupport && ( + + {t('contact_us')} + + )} + + {t('give_feedback')} + + + ) +} diff --git a/services/web/frontend/js/features/ide-redesign/contexts/rail-context.tsx b/services/web/frontend/js/features/ide-redesign/contexts/rail-context.tsx index 15e0df035c..fe622991ed 100644 --- a/services/web/frontend/js/features/ide-redesign/contexts/rail-context.tsx +++ b/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> + activeModal: RailModalKey | null + setActiveModal: Dispatch> } | 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( + null + ) + const setActiveModal: Dispatch> = + useCallback(modalKey => { + setActiveModalInternal(modalKey) + }, []) + const panelRef = useRef(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, ] ) diff --git a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/rail.scss b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/rail.scss index 69a7e5390a..8410ae853a 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/rail.scss +++ b/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; + } +} diff --git a/services/web/locales/en.json b/services/web/locales/en.json index 8b615beeb0..19216795cc 100644 --- a/services/web/locales/en.json +++ b/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{...} command. This makes it easy to reference figures without needing to manually remember the figure numbering. <1>Learn more",