diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index 5d3bbf00df..df68da6ec1 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -441,6 +441,7 @@ "editor_disconected_click_to_reconnect": "", "editor_limit_exceeded_in_this_project": "", "editor_only_hide_pdf": "", + "editor_settings": "", "editor_theme": "", "educational_disclaimer": "", "educational_disclaimer_heading": "", @@ -554,6 +555,8 @@ "full_project_search": "", "full_width": "", "future_payments": "", + "general": "", + "general_settings": "", "generate_token": "", "generic_if_problem_continues_contact_us": "", "generic_linked_file_compile_error": "", @@ -757,6 +760,8 @@ "institutional_leavers_survey_notification": "", "integrations": "", "interested_in_cheaper_personal_plan": "", + "interface": "", + "interface_settings": "", "invalid_confirmation_code": "", "invalid_email": "", "invalid_file_name": "", @@ -1083,6 +1088,7 @@ "pay_now": "", "payment_provider_unreachable_error": "", "payment_summary": "", + "pdf": "", "pdf_compile_in_progress_error": "", "pdf_compile_rate_limit_hit": "", "pdf_compile_try_again": "", @@ -1090,6 +1096,7 @@ "pdf_only_hide_editor": "", "pdf_preview_error": "", "pdf_rendering_error": "", + "pdf_settings": "", "pdf_unavailable_for_download": "", "pdf_viewer": "", "pdf_viewer_error": "", diff --git a/services/web/frontend/fonts/material-symbols/MaterialSymbolsRoundedUnfilledPartialSlice.woff2 b/services/web/frontend/fonts/material-symbols/MaterialSymbolsRoundedUnfilledPartialSlice.woff2 index 6eba080dd6..68ddbc999d 100644 Binary files a/services/web/frontend/fonts/material-symbols/MaterialSymbolsRoundedUnfilledPartialSlice.woff2 and b/services/web/frontend/fonts/material-symbols/MaterialSymbolsRoundedUnfilledPartialSlice.woff2 differ diff --git a/services/web/frontend/fonts/material-symbols/unfilled-symbols.mjs b/services/web/frontend/fonts/material-symbols/unfilled-symbols.mjs index 7463addc64..54052d8773 100644 --- a/services/web/frontend/fonts/material-symbols/unfilled-symbols.mjs +++ b/services/web/frontend/fonts/material-symbols/unfilled-symbols.mjs @@ -3,9 +3,14 @@ // to update the font file with the latest icons. export default /** @type {const} */ ([ + 'code', 'description', 'forum', + 'help', 'integration_instructions', + 'picture_as_pdf', 'rate_review', 'report', + 'settings', + 'web_asset', ]) diff --git a/services/web/frontend/js/features/ide-react/components/layout/ide-page.tsx b/services/web/frontend/js/features/ide-react/components/layout/ide-page.tsx index c73c5fe1ef..84f8ba87b2 100644 --- a/services/web/frontend/js/features/ide-react/components/layout/ide-page.tsx +++ b/services/web/frontend/js/features/ide-react/components/layout/ide-page.tsx @@ -16,6 +16,9 @@ import { useFeatureFlag } from '@/shared/context/split-test-context' const MainLayoutNew = lazy( () => import('@/features/ide-redesign/components/main-layout') ) +const SettingsModalNew = lazy( + () => import('@/features/ide-redesign/components/settings/settings-modal') +) export default function IdePage() { useLayoutEventTracking() // sent event when the layout changes @@ -33,6 +36,7 @@ export default function IdePage() { {newEditor ? ( + ) : ( 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 a588dfa40d..e9d21af972 100644 --- a/services/web/frontend/js/features/ide-redesign/components/rail.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/rail.tsx @@ -1,9 +1,10 @@ -import { ReactElement, useCallback, useState } from 'react' +import { ReactElement, useCallback, useMemo, useState } from 'react' import { Nav, NavLink, Tab, TabContainer } from 'react-bootstrap-5' import MaterialIcon, { AvailableUnfilledIcon, } from '@/shared/components/material-icon' import { Panel } from 'react-resizable-panels' +import { useLayoutContext } from '@/shared/context/layout-context' type RailElement = { icon: AvailableUnfilledIcon @@ -11,6 +12,14 @@ type RailElement = { component: ReactElement } +type RailActionLink = { key: string; icon: AvailableUnfilledIcon; href: string } +type RailActionButton = { + key: string + icon: AvailableUnfilledIcon + action: () => void +} +type RailAction = RailActionLink | RailActionButton + const RAIL_TABS: RailElement[] = [ // NOTE: The file tree **MUST** be the first (i.e. default) tab in the list // since the file tree is responsible for opening the initial document. @@ -45,6 +54,19 @@ export const RailLayout = () => { const [selectedTab, setSelectedTab] = useState( RAIL_TABS[0]?.key ) + const { setLeftMenuShown } = useLayoutContext() + const railActions: RailAction[] = useMemo( + () => [ + { key: 'support', icon: 'help', href: '/learn' }, + { + key: 'settings', + icon: 'settings', + action: () => setLeftMenuShown(true), + }, + ], + [setLeftMenuShown] + ) + return ( { id="ide-rail-tabs" >
-
) } + +const RailActionElement = ({ action }: { action: RailAction }) => { + const icon = ( + + ) + const onActionClick = useCallback(() => { + if ('action' in action) { + action.action() + } + }, [action]) + + if ('href' in action) { + return ( + + {icon} + + ) + } else { + return ( + + ) + } +} diff --git a/services/web/frontend/js/features/ide-redesign/components/settings/settings-modal-body.tsx b/services/web/frontend/js/features/ide-redesign/components/settings/settings-modal-body.tsx new file mode 100644 index 0000000000..c5143a2932 --- /dev/null +++ b/services/web/frontend/js/features/ide-redesign/components/settings/settings-modal-body.tsx @@ -0,0 +1,147 @@ +import MaterialIcon, { + AvailableUnfilledIcon, +} from '@/shared/components/material-icon' +import { ReactElement, useMemo, useState } from 'react' +import { + Nav, + NavLink, + TabContainer, + TabContent, + TabPane, +} from 'react-bootstrap-5' +import { useTranslation } from 'react-i18next' + +export type SettingsEntry = SettingsLink | SettingsTab + +type SettingsTab = { + icon: AvailableUnfilledIcon + key: string + component: ReactElement + title: string + subtitle: string +} + +type SettingsLink = { + key: string + icon: AvailableUnfilledIcon + href: string + title: string +} + +export const SettingsModalBody = () => { + const { t } = useTranslation() + const settingsTabs: SettingsEntry[] = useMemo( + () => [ + { + key: 'general', + title: t('general'), + subtitle: t('general_settings'), + icon: 'settings', + component:
General
, + }, + { + key: 'editor', + title: t('editor'), + subtitle: t('editor_settings'), + icon: 'code', + component:
Editor
, + }, + { + key: 'pdf', + title: t('pdf'), + subtitle: t('pdf_settings'), + icon: 'picture_as_pdf', + component:
PDF
, + }, + { + key: 'interface', + title: t('interface'), + subtitle: t('interface_settings'), + icon: 'web_asset', + component:
Interface
, + }, + { + key: 'account_settings', + title: t('account_settings'), + icon: 'settings', + href: '/user/settings', + }, + ], + [t] + ) + const [activeTab, setActiveTab] = useState( + settingsTabs[0]?.key + ) + + return ( + +
+ + + {settingsTabs + .filter(t => 'component' in t) + .map(({ key, component, subtitle }) => ( + +

{subtitle}

+
{component}
+
+ ))} +
+
+
+ ) +} + +const SettingsNavLink = ({ entry }: { entry: SettingsEntry }) => { + if ('href' in entry) { + return ( + + + {entry.title} +
+ + + ) + } else { + return ( + <> + + + {entry.title} + + + ) + } +} diff --git a/services/web/frontend/js/features/ide-redesign/components/settings/settings-modal.tsx b/services/web/frontend/js/features/ide-redesign/components/settings/settings-modal.tsx new file mode 100644 index 0000000000..8ee804159f --- /dev/null +++ b/services/web/frontend/js/features/ide-redesign/components/settings/settings-modal.tsx @@ -0,0 +1,31 @@ +import OLModal, { + OLModalBody, + OLModalHeader, + OLModalTitle, +} from '@/features/ui/components/ol/ol-modal' +import { useLayoutContext } from '@/shared/context/layout-context' +import { useTranslation } from 'react-i18next' +import { SettingsModalBody } from './settings-modal-body' + +const SettingsModal = () => { + // TODO ide-redesign-cleanup: Either rename the field, or introduce a separate + // one + const { leftMenuShown, setLeftMenuShown } = useLayoutContext() + const { t } = useTranslation() + return ( + setLeftMenuShown(false)} + size="lg" + > + + {t('settings')} + + + + + + ) +} + +export default SettingsModal diff --git a/services/web/frontend/stylesheets/bootstrap-5/pages/all.scss b/services/web/frontend/stylesheets/bootstrap-5/pages/all.scss index 956f28777c..454f0708c5 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/pages/all.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/pages/all.scss @@ -8,6 +8,7 @@ @import 'editor/ide'; @import 'editor/ide-redesign'; @import 'editor/rail'; +@import 'editor/settings'; @import 'editor/toolbar'; @import 'editor/online-users'; @import 'editor/hotkeys'; 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 17a8cd2ba7..ba7f5b8202 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/rail.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/rail.scss @@ -6,6 +6,11 @@ --ide-rail-link-active-indicator-background: var(--neutral-90); } +.ide-rail-tab-button { + border: 0; + background: none; +} + .ide-rail-tab-link { border-radius: 12px; display: block; @@ -55,3 +60,7 @@ height: 100%; border: 1px solid var(--border-divider); } + +.ide-rail-tabs-nav { + height: 100%; +} diff --git a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/settings.scss b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/settings.scss new file mode 100644 index 0000000000..75aba658e6 --- /dev/null +++ b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/settings.scss @@ -0,0 +1,45 @@ +.ide-settings-tab-nav.nav { + width: 240px; + border-right: var(--bs-modal-header-border-width) solid + var(--bs-modal-header-border-color); + padding: var(--spacing-02); + gap: var(--spacing-02); +} + +.ide-settings-tab-content { + padding: var(--spacing-06) var(--spacing-08); + max-height: 75%; + height: 400px; +} + +.ide-settings-tab-subtitle { + font-size: var(--font-size-04); + line-height: var(--line-height-03); + padding: var(--spacing-06) var(--spacing-08); +} + +.ide-settings-tab-link { + display: flex; + align-items: flex-start; + flex-direction: row; + gap: var(--spacing-02); + color: var(--neutral-90); + padding: var(--spacing-02); + border-radius: var(--border-radius-base); + font-size: var(--font-size-02); + line-height: var(--line-height-02); + text-decoration: none; + + &:visited { + color: var(--neutral-90); + } + + &.active { + color: #fff; + background-color: var(--neutral-90); + } +} + +.ide-settings-modal-body { + padding: 0; +} diff --git a/services/web/locales/en.json b/services/web/locales/en.json index f758c24979..2931f6669c 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -576,6 +576,7 @@ "editor_disconected_click_to_reconnect": "Editor disconnected, click anywhere to reconnect.", "editor_limit_exceeded_in_this_project": "Too many editors in this project", "editor_only_hide_pdf": "Editor only <0>(hide PDF)", + "editor_settings": "Editor settings", "editor_theme": "Editor theme", "educational_disclaimer": "I confirm that users will be students or faculty using Overleaf primarily for study and teaching, and can provide evidence of this if requested.", "educational_disclaimer_heading": "Educational discount confirmation", @@ -749,6 +750,8 @@ "gallery_page_items_lowercase": "gallery items", "gallery_page_title": "Gallery - Templates, Examples and Articles written in LaTeX", "gallery_show_more_tags": "Show more", + "general": "General", + "general_settings": "General settings", "generate_token": "Generate token", "generic_if_problem_continues_contact_us": "If the problem continues please contact us", "generic_linked_file_compile_error": "This project’s output files are not available because it failed to compile. Please open the project to see the compilation error details.", @@ -995,6 +998,8 @@ "institutional_login_unknown": "Sorry, we don’t know which institution issued that email address. You can browse our list of institutions to find yours, or you can use one of the other options below.", "integrations": "Integrations", "interested_in_cheaper_personal_plan": "Would you be interested in the cheaper <0>__price__ Personal plan?", + "interface": "Interface", + "interface_settings": "Interface settings", "invalid_certificate": "Invalid certificate. Please check the certificate and try again.", "invalid_confirmation_code": "That didn’t work. Please check the code and try again.", "invalid_email": "An email address is invalid", @@ -1468,6 +1473,7 @@ "payment_method_accepted": "__paymentMethod__ accepted", "payment_provider_unreachable_error": "Sorry, there was an error talking to our payment provider. Please try again in a few moments.\nIf you are using any ad or script blocking extensions in your browser, you may need to temporarily disable them.", "payment_summary": "Payment summary", + "pdf": "PDF", "pdf_compile_in_progress_error": "A previous compile is still running. Please wait a minute and try compiling again.", "pdf_compile_rate_limit_hit": "Compile rate limit hit", "pdf_compile_try_again": "Please wait for your other compile to finish before trying again.", @@ -1475,6 +1481,7 @@ "pdf_only_hide_editor": "PDF only <0>(hide editor)", "pdf_preview_error": "There was a problem displaying the compilation results for this project.", "pdf_rendering_error": "PDF Rendering Error", + "pdf_settings": "PDF settings", "pdf_unavailable_for_download": "PDF unavailable for download", "pdf_viewer": "PDF Viewer", "pdf_viewer_error": "There was a problem displaying the PDF for this project.",