diff --git a/services/web/config/settings.defaults.js b/services/web/config/settings.defaults.js index d74bd88b08..4cd7aa7cdc 100644 --- a/services/web/config/settings.defaults.js +++ b/services/web/config/settings.defaults.js @@ -983,6 +983,7 @@ module.exports = { toastGenerators: [], editorSidebarComponents: [], fileTreeToolbarComponents: [], + integrationPanelComponents: [], }, moduleImportSequence: [ diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index fa3e53a789..cb2373dcec 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -301,6 +301,7 @@ "confirming": "", "conflicting_paths_found": "", "congratulations_youve_successfully_join_group": "", + "connect_overleaf_with_github": "", "connected_users": "", "connection_lost": "", "contact_group_admin": "", @@ -423,6 +424,7 @@ "draft_sso_configuration": "", "drag_here": "", "drag_here_paste_an_image_or": "", + "dropbox": "", "dropbox_checking_sync_status": "", "dropbox_duplicate_project_names": "", "dropbox_duplicate_project_names_suggestion": "", @@ -615,6 +617,7 @@ "git_bridge_modal_use_previous_token": "", "git_integration": "", "git_integration_info": "", + "github": "", "github_commit_message_placeholder": "", "github_credentials_expired": "", "github_empty_repository_error": "", @@ -791,6 +794,7 @@ "institution_has_overleaf_subscription": "", "institution_templates": "", "institutional_leavers_survey_notification": "", + "integrate_overleaf_with_dropbox": "", "integrations": "", "integrations_like_github": "", "interested_in_cheaper_personal_plan": "", @@ -888,6 +892,7 @@ "link_accounts": "", "link_accounts_and_add_email": "", "link_institutional_email_get_started": "", + "link_overleaf_with_git": "", "link_sharing": "", "link_sharing_is_off_short": "", "link_sharing_is_on": "", @@ -1191,6 +1196,7 @@ "plus_x_additional_licenses_for_a_total_of_y_users": "", "postal_code": "", "postal_code_sentence_case": "", + "premium": "", "premium_feature": "", "premium_features": "", "premium_plan_label": "", diff --git a/services/web/frontend/js/features/ide-react/context/react-context-root.tsx b/services/web/frontend/js/features/ide-react/context/react-context-root.tsx index 22b10384b4..9f199373cb 100644 --- a/services/web/frontend/js/features/ide-react/context/react-context-root.tsx +++ b/services/web/frontend/js/features/ide-react/context/react-context-root.tsx @@ -17,7 +17,7 @@ import { OnlineUsersProvider } from '@/features/ide-react/context/online-users-c import { OutlineProvider } from '@/features/ide-react/context/outline-context' import { PermissionsProvider } from '@/features/ide-react/context/permissions-context' import { ProjectProvider } from '@/shared/context/project-context' -import { RailTabProvider } from '@/features/ide-redesign/contexts/rail-tab-context' +import { RailProvider } from '@/features/ide-redesign/contexts/rail-context' import { ProjectSettingsProvider } from '@/features/editor-left-menu/context/project-settings-context' import { ReferencesProvider } from '@/features/ide-react/context/references-context' import { SnapshotProvider } from '@/features/ide-react/context/snapshot-context' @@ -49,7 +49,7 @@ export const ReactContextRoot: FC<{ providers?: Record }> = ({ PermissionsProvider, ProjectProvider, ProjectSettingsProvider, - RailTabProvider, + RailProvider, ReferencesProvider, SnapshotProvider, SplitTestProvider, @@ -83,9 +83,9 @@ export const ReactContextRoot: FC<{ providers?: Record }> = ({ - + {children} - + diff --git a/services/web/frontend/js/features/ide-redesign/components/integrations-panel/integration-card.tsx b/services/web/frontend/js/features/ide-redesign/components/integrations-panel/integration-card.tsx new file mode 100644 index 0000000000..6b0f69a48f --- /dev/null +++ b/services/web/frontend/js/features/ide-redesign/components/integrations-panel/integration-card.tsx @@ -0,0 +1,42 @@ +import OLBadge from '@/features/ui/components/ol/ol-badge' +import MaterialIcon from '@/shared/components/material-icon' +import { useTranslation } from 'react-i18next' + +export default function IntegrationCard({ + onClick, + title, + description, + icon, + showPaywallBadge, +}: { + onClick: () => void + title: string + description: string + icon: React.ReactNode + showPaywallBadge: boolean +}) { + const { t } = useTranslation() + + return ( + + ) +} diff --git a/services/web/frontend/js/features/ide-redesign/components/integrations-panel/integrations-panel.tsx b/services/web/frontend/js/features/ide-redesign/components/integrations-panel/integrations-panel.tsx new file mode 100644 index 0000000000..bf25593387 --- /dev/null +++ b/services/web/frontend/js/features/ide-redesign/components/integrations-panel/integrations-panel.tsx @@ -0,0 +1,29 @@ +import { ElementType } from 'react' +import importOverleafModules from '../../../../../macros/import-overleaf-module.macro' +import MaterialIcon from '@/shared/components/material-icon' +import OlButton from '@/features/ui/components/ol/ol-button' +import { useRailContext } from '../../contexts/rail-context' + +const integrationPanelComponents = importOverleafModules( + 'integrationPanelComponents' +) as { import: { default: ElementType }; path: string }[] + +export default function IntegrationsPanel() { + const { handlePaneCollapse } = useRailContext() + + return ( +
+
+

Integrations

+ + + +
+ {integrationPanelComponents.map( + ({ import: { default: Component }, path }) => ( + + ) + )} +
+ ) +} diff --git a/services/web/frontend/js/features/ide-redesign/components/pdf-preview/pdf-error-state.tsx b/services/web/frontend/js/features/ide-redesign/components/pdf-preview/pdf-error-state.tsx index 329aab1ba6..cb87774945 100644 --- a/services/web/frontend/js/features/ide-redesign/components/pdf-preview/pdf-error-state.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/pdf-preview/pdf-error-state.tsx @@ -1,7 +1,7 @@ import OLButton from '@/features/ui/components/ol/ol-button' import MaterialIcon from '@/shared/components/material-icon' import { useTranslation } from 'react-i18next' -import { useRailTabContext } from '../../contexts/rail-tab-context' +import { useRailContext } from '../../contexts/rail-context' import { usePdfPreviewContext } from '@/features/pdf-preview/components/pdf-preview-provider' import { useDetachCompileContext as useCompileContext } from '@/shared/context/detach-compile-context' import { useFeatureFlag } from '@/shared/context/split-test-context' @@ -11,7 +11,7 @@ function PdfErrorState() { // TODO ide-redesign-cleanup: rename showLogs to something else and check usages const { showLogs } = useCompileContext() const { t } = useTranslation() - const { setSelectedTab: setSelectedRailTab } = useRailTabContext() + const { setSelectedTab: setSelectedRailTab } = useRailContext() const newEditor = useFeatureFlag('editor-redesign') if (!newEditor || (!loadingError && !showLogs)) { 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 fe3114e375..c9d46ac4ed 100644 --- a/services/web/frontend/js/features/ide-redesign/components/rail.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/rail.tsx @@ -6,15 +6,15 @@ import MaterialIcon, { import { Panel } from 'react-resizable-panels' import { useLayoutContext } from '@/shared/context/layout-context' import { ErrorIndicator, ErrorPane } from './errors' -import { RailTabKey, useRailTabContext } from '../contexts/rail-tab-context' +import { RailTabKey, useRailContext } from '../contexts/rail-context' import FileTreeOutlinePanel from './file-tree-outline-panel' import { ChatIndicator, ChatPane } from './chat' import getMeta from '@/utils/meta' import { HorizontalResizeHandle } from '@/features/ide-react/components/resize/horizontal-resize-handle' import { HorizontalToggler } from '@/features/ide-react/components/resize/horizontal-toggler' -import { useRail } from '../hooks/use-rail' import { useTranslation } from 'react-i18next' import classNames from 'classnames' +import IntegrationsPanel from './integrations-panel/integrations-panel' type RailElement = { icon: AvailableUnfilledIcon @@ -41,7 +41,7 @@ const RAIL_TABS: RailElement[] = [ { key: 'integrations', icon: 'integration_instructions', - component: <>Integrations, + component: , }, { key: 'review-panel', @@ -66,14 +66,15 @@ const RAIL_TABS: RailElement[] = [ export const RailLayout = () => { const { t } = useTranslation() const { + selectedTab, + setSelectedTab, isOpen, setIsOpen, panelRef, handlePaneCollapse, handlePaneExpand, togglePane, - } = useRail() - const { selectedTab, setSelectedTab } = useRailTabContext() + } = useRailContext() const { setLeftMenuShown } = useLayoutContext() const railActions: RailAction[] = useMemo( 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 new file mode 100644 index 0000000000..15e0df035c --- /dev/null +++ b/services/web/frontend/js/features/ide-redesign/contexts/rail-context.tsx @@ -0,0 +1,96 @@ +import useCollapsiblePanel from '@/features/ide-react/hooks/use-collapsible-panel' +import { + createContext, + Dispatch, + FC, + SetStateAction, + useCallback, + useContext, + useMemo, + useRef, + useState, +} from 'react' +import { ImperativePanelHandle } from 'react-resizable-panels' + +export type RailTabKey = + | 'file-tree' + | 'integrations' + | 'review-panel' + | 'chat' + | 'errors' + +const RailContext = createContext< + | { + selectedTab: RailTabKey + setSelectedTab: Dispatch> + isOpen: boolean + setIsOpen: Dispatch> + panelRef: React.RefObject + togglePane: () => void + handlePaneExpand: () => void + handlePaneCollapse: () => void + resizing: boolean + setResizing: Dispatch> + } + | undefined +>(undefined) + +export const RailProvider: FC = ({ children }) => { + const [isOpen, setIsOpen] = useState(true) + const [resizing, setResizing] = useState(false) + const panelRef = useRef(null) + useCollapsiblePanel(isOpen, panelRef) + + const togglePane = useCallback(() => { + setIsOpen(value => !value) + }, []) + + const handlePaneExpand = useCallback(() => { + setIsOpen(true) + }, []) + + const handlePaneCollapse = useCallback(() => { + setIsOpen(false) + }, []) + + // NOTE: The file tree **MUST** be the first tab to be opened + // since it is responsible for opening the initial document. + const [selectedTab, setSelectedTab] = useState('file-tree') + + const value = useMemo( + () => ({ + selectedTab, + setSelectedTab, + isOpen, + setIsOpen, + panelRef, + togglePane, + handlePaneExpand, + handlePaneCollapse, + resizing, + setResizing, + }), + [ + selectedTab, + setSelectedTab, + isOpen, + setIsOpen, + panelRef, + togglePane, + handlePaneExpand, + handlePaneCollapse, + resizing, + setResizing, + ] + ) + + return {children} +} + +export const useRailContext = () => { + const context = useContext(RailContext) + if (!context) { + throw new Error('useRailContext is only available inside RailProvider') + } + return context +} diff --git a/services/web/frontend/js/features/ide-redesign/contexts/rail-tab-context.tsx b/services/web/frontend/js/features/ide-redesign/contexts/rail-tab-context.tsx deleted file mode 100644 index 868ab0e441..0000000000 --- a/services/web/frontend/js/features/ide-redesign/contexts/rail-tab-context.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { - createContext, - Dispatch, - FC, - SetStateAction, - useContext, - useMemo, - useState, -} from 'react' - -export type RailTabKey = - | 'file-tree' - | 'integrations' - | 'review-panel' - | 'chat' - | 'errors' - -const RailTabContext = createContext< - | { - selectedTab: RailTabKey - setSelectedTab: Dispatch> - } - | undefined ->(undefined) - -export const RailTabProvider: FC = ({ children }) => { - // NOTE: The file tree **MUST** be the first tab to be opened - // since it is responsible for opening the initial document. - const [selectedTab, setSelectedTab] = useState('file-tree') - - const value = useMemo( - () => ({ - selectedTab, - setSelectedTab, - }), - [selectedTab, setSelectedTab] - ) - - return ( - {children} - ) -} - -export const useRailTabContext = () => { - const context = useContext(RailTabContext) - if (!context) { - throw new Error( - 'useRailTabContext is only available inside RailTabProvider' - ) - } - return context -} diff --git a/services/web/frontend/js/features/ide-redesign/hooks/use-pdf-pane.tsx b/services/web/frontend/js/features/ide-redesign/hooks/use-pdf-pane.tsx index ecab1ac294..8215af3fdd 100644 --- a/services/web/frontend/js/features/ide-redesign/hooks/use-pdf-pane.tsx +++ b/services/web/frontend/js/features/ide-redesign/hooks/use-pdf-pane.tsx @@ -1,7 +1,35 @@ -import { useRail } from './use-rail' +import { useCallback, useRef, useState } from 'react' +import useCollapsiblePanel from '@/features/ide-react/hooks/use-collapsible-panel' +import { ImperativePanelHandle } from 'react-resizable-panels' +// FIXME: This is temporary, to avoid clashing with the existing usePdfPane +// which uses the layout context. That's the correct approach. export const usePdfPane = () => { - // FIXME: This is temporary, to avoid clashing with the existing usePdfPane - // which uses the layout context. That's the correct approach. - return useRail() + const [isOpen, setIsOpen] = useState(true) + const [resizing, setResizing] = useState(false) + const panelRef = useRef(null) + useCollapsiblePanel(isOpen, panelRef) + + const togglePane = useCallback(() => { + setIsOpen(value => !value) + }, []) + + const handlePaneExpand = useCallback(() => { + setIsOpen(true) + }, []) + + const handlePaneCollapse = useCallback(() => { + setIsOpen(false) + }, []) + + return { + isOpen, + setIsOpen, + panelRef, + togglePane, + handlePaneExpand, + handlePaneCollapse, + resizing, + setResizing, + } } diff --git a/services/web/frontend/js/features/ide-redesign/hooks/use-rail.tsx b/services/web/frontend/js/features/ide-redesign/hooks/use-rail.tsx deleted file mode 100644 index 771a20d672..0000000000 --- a/services/web/frontend/js/features/ide-redesign/hooks/use-rail.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { useCallback, useRef, useState } from 'react' -import useCollapsiblePanel from '@/features/ide-react/hooks/use-collapsible-panel' -import { ImperativePanelHandle } from 'react-resizable-panels' - -export const useRail = () => { - const [isOpen, setIsOpen] = useState(true) - const [resizing, setResizing] = useState(false) - const panelRef = useRef(null) - useCollapsiblePanel(isOpen, panelRef) - - const togglePane = useCallback(() => { - setIsOpen(value => !value) - }, []) - - const handlePaneExpand = useCallback(() => { - setIsOpen(true) - }, []) - - const handlePaneCollapse = useCallback(() => { - setIsOpen(false) - }, []) - - return { - isOpen, - setIsOpen, - panelRef, - togglePane, - handlePaneExpand, - handlePaneCollapse, - resizing, - setResizing, - } -} diff --git a/services/web/frontend/js/shared/svgs/dropbox-logo.jsx b/services/web/frontend/js/shared/svgs/dropbox-logo.tsx similarity index 92% rename from services/web/frontend/js/shared/svgs/dropbox-logo.jsx rename to services/web/frontend/js/shared/svgs/dropbox-logo.tsx index bcd13b6ec1..2142ebe73d 100644 --- a/services/web/frontend/js/shared/svgs/dropbox-logo.jsx +++ b/services/web/frontend/js/shared/svgs/dropbox-logo.tsx @@ -1,8 +1,8 @@ -function DropboxLogo() { +function DropboxLogo({ size = 40 }: { size?: number }) { return ( list of institutions to find yours, or you can use one of the other options below.", + "integrate_overleaf_with_dropbox": "Integrate __appName__ with Dropbox to sync your LaTeX projects and keep files up to date automatically.", "integrations": "Integrations", "integrations_like_github": "Integrations like GitHub Sync", "interested_in_cheaper_personal_plan": "Would you be interested in the cheaper <0>__price__ Personal plan?", @@ -1168,6 +1170,7 @@ "link_accounts": "Link Accounts", "link_accounts_and_add_email": "Link Accounts and Add Email", "link_institutional_email_get_started": "Link an institutional email address to your account to get started.", + "link_overleaf_with_git": "Link __appName__ with Git for seamless project syncing and version control across your repositories.", "link_sharing": "Link sharing", "link_sharing_is_off_short": "Link sharing is off", "link_sharing_is_on": "Link sharing is on", @@ -1599,6 +1602,7 @@ "position": "Position", "postal_code": "Postal Code", "postal_code_sentence_case": "Postal code", + "premium": "Premium", "premium_feature": "Premium feature", "premium_features": "Premium features", "premium_plan_label": "You’re using Overleaf Premium",