Procházet zdrojové kódy

Merge pull request #22928 from overleaf/mj-ide-toolbar

[web] Initial editor toolbar

GitOrigin-RevId: 33658d886bf86cbaa22a161f2ff463ef608e5f6b
Mathias Jakobsen před 1 rokem
rodič
revize
4b46120ec1

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

@@ -507,6 +507,7 @@
   "failed_to_send_sso_link_invite_to_email": "",
   "fast": "",
   "features_like_track_changes": "",
+  "file": "",
   "file_action_created": "",
   "file_action_deleted": "",
   "file_action_edited": "",
@@ -1082,6 +1083,7 @@
   "overleaf": "",
   "overleaf_history_system": "",
   "overleaf_labs": "",
+  "overleaf_logo": "",
   "overview": "",
   "overwrite": "",
   "overwriting_the_original_folder": "",
@@ -1210,6 +1212,7 @@
   "project_timed_out_intro": "",
   "project_timed_out_learn_more": "",
   "project_timed_out_optimize_images": "",
+  "project_title_options": "",
   "project_too_large": "",
   "project_too_large_please_reduce": "",
   "project_too_much_editable_text": "",
@@ -1863,6 +1866,7 @@
   "vat": "",
   "vat_number": "",
   "verify_email_address_before_enabling_managed_users": "",
+  "view": "",
   "view_all": "",
   "view_code": "",
   "view_configuration": "",

+ 1 - 1
services/web/frontend/js/features/ide-react/context/online-users-context.tsx

@@ -21,7 +21,7 @@ import { debugConsole } from '@/utils/debugging'
 import { IdeEvents } from '@/features/ide-react/create-ide-event-emitter'
 import { getHueForUserId } from '@/shared/utils/colors'
 
-type OnlineUser = {
+export type OnlineUser = {
   id: string
   user_id: string
   email: string

+ 2 - 1
services/web/frontend/js/features/ide-redesign/components/main-layout.tsx

@@ -4,11 +4,12 @@ import { HorizontalResizeHandle } from '@/features/ide-react/components/resize/h
 import PdfPreview from '@/features/pdf-preview/components/pdf-preview'
 import { Editor } from './editor'
 import { RailLayout } from './rail'
+import { Toolbar } from './toolbar/toolbar'
 
 export default function MainLayout() {
   return (
     <div className="ide-redesign-main">
-      <div className="ide-skeleton-block">Toolbar</div>
+      <Toolbar />
       <div className="ide-redesign-body">
         <PanelGroup
           autoSaveId="ide-redesign-outer-layout"

+ 77 - 0
services/web/frontend/js/features/ide-redesign/components/toolbar/menu-bar.tsx

@@ -0,0 +1,77 @@
+import {
+  Dropdown,
+  DropdownDivider,
+  DropdownMenu,
+  DropdownToggle,
+} from '@/features/ui/components/bootstrap-5/dropdown-menu'
+import OLDropdownMenuItem from '@/features/ui/components/ol/ol-dropdown-menu-item'
+import { FC } from 'react'
+import { useTranslation } from 'react-i18next'
+
+type MenuBarOptionProps = {
+  title: string
+  onClick?: () => void
+}
+
+type MenuBarDropdownProps = {
+  title: string
+  id: string
+}
+
+export const ToolbarMenuBar = () => {
+  const { t } = useTranslation()
+  return (
+    <div className="ide-redesign-toolbar-menu-bar">
+      <MenuBarDropdown title={t('file')} id="file">
+        <MenuBarOption title="New File" />
+        <MenuBarOption title="New Project" />
+      </MenuBarDropdown>
+      <MenuBarDropdown title={t('edit')} id="edit">
+        <MenuBarOption title="Undo" />
+        <MenuBarOption title="Redo" />
+        <DropdownDivider />
+        <MenuBarOption title="Cut" />
+        <MenuBarOption title="Copy" />
+        <MenuBarOption title="Pate" />
+      </MenuBarDropdown>
+      <MenuBarDropdown title={t('view')} id="view">
+        <MenuBarOption title="PDF only" />
+      </MenuBarDropdown>
+      <MenuBarDropdown title={t('insert')} id="insert">
+        <MenuBarOption title="Insert figure" />
+        <MenuBarOption title="Insert table" />
+        <MenuBarOption title="Insert link" />
+        <MenuBarOption title="Add comment" />
+      </MenuBarDropdown>
+      <MenuBarDropdown title={t('format')} id="format">
+        <MenuBarOption title="Bold text" />
+      </MenuBarDropdown>
+      <MenuBarDropdown title={t('help')} id="help">
+        <MenuBarOption title="Keyboard shortcuts" />
+        <MenuBarOption title="Documentation" />
+        <DropdownDivider />
+        <MenuBarOption title="Contact us" />
+        <MenuBarOption title="Give feedback" />
+      </MenuBarDropdown>
+    </div>
+  )
+}
+
+const MenuBarDropdown: FC<MenuBarDropdownProps> = ({ title, children, id }) => {
+  return (
+    <Dropdown align="start">
+      <DropdownToggle
+        id={`toolbar-menu-bar-item-${id}`}
+        variant="secondary"
+        className="ide-redesign-toolbar-dropdown-toggle-subdued"
+      >
+        {title}
+      </DropdownToggle>
+      <DropdownMenu>{children}</DropdownMenu>
+    </Dropdown>
+  )
+}
+
+const MenuBarOption = ({ title, onClick }: MenuBarOptionProps) => {
+  return <OLDropdownMenuItem onClick={onClick}>{title}</OLDropdownMenuItem>
+}

+ 27 - 0
services/web/frontend/js/features/ide-redesign/components/toolbar/online-users.tsx

@@ -0,0 +1,27 @@
+import OnlineUsersWidget from '@/features/editor-navigation-toolbar/components/online-users-widget'
+import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context'
+import {
+  OnlineUser,
+  useOnlineUsersContext,
+} from '@/features/ide-react/context/online-users-context'
+import { useCallback } from 'react'
+
+export const OnlineUsers = () => {
+  const { openDoc } = useEditorManagerContext()
+  const { onlineUsersArray } = useOnlineUsersContext()
+
+  const goToUser = useCallback(
+    (user: OnlineUser) => {
+      if (user.doc && typeof user.row === 'number') {
+        openDoc(user.doc, { gotoLine: user.row + 1 })
+      }
+    },
+    [openDoc]
+  )
+
+  return (
+    <div className="ide-redesign-online-users">
+      <OnlineUsersWidget onlineUsers={onlineUsersArray} goToUser={goToUser} />
+    </div>
+  )
+}

+ 39 - 0
services/web/frontend/js/features/ide-redesign/components/toolbar/project-title.tsx

@@ -0,0 +1,39 @@
+import {
+  Dropdown,
+  DropdownDivider,
+  DropdownMenu,
+  DropdownToggle,
+} from '@/features/ui/components/bootstrap-5/dropdown-menu'
+import OLDropdownMenuItem from '@/features/ui/components/ol/ol-dropdown-menu-item'
+import MaterialIcon from '@/shared/components/material-icon'
+import { useProjectContext } from '@/shared/context/project-context'
+import { useTranslation } from 'react-i18next'
+
+export const ToolbarProjectTitle = () => {
+  const { name } = useProjectContext()
+  const { t } = useTranslation()
+  return (
+    <Dropdown align="start">
+      <DropdownToggle
+        id="project-title-options"
+        className="ide-redesign-toolbar-dropdown-toggle-subdued"
+        variant="secondary"
+      >
+        {name}
+        <MaterialIcon
+          type="keyboard_arrow_down"
+          accessibilityLabel={t('project_title_options')}
+        />
+      </DropdownToggle>
+      <DropdownMenu>
+        <OLDropdownMenuItem>TODO: Export</OLDropdownMenuItem>
+        <DropdownDivider />
+        <OLDropdownMenuItem>{t('rename')}</OLDropdownMenuItem>
+        <OLDropdownMenuItem>{t('download')}</OLDropdownMenuItem>
+        <OLDropdownMenuItem className="text-danger">
+          {t('delete')}
+        </OLDropdownMenuItem>
+      </DropdownMenu>
+    </Dropdown>
+  )
+}

+ 68 - 0
services/web/frontend/js/features/ide-redesign/components/toolbar/toolbar.tsx

@@ -0,0 +1,68 @@
+import OLButton from '@/features/ui/components/ol/ol-button'
+import MaterialIcon from '@/shared/components/material-icon'
+import { useTranslation } from 'react-i18next'
+import { ToolbarMenuBar } from './menu-bar'
+import { ToolbarProjectTitle } from './project-title'
+import { OnlineUsers } from './online-users'
+
+export const Toolbar = () => {
+  return (
+    <div className="ide-redesign-toolbar">
+      <ToolbarMenus />
+      <ToolbarProjectTitle />
+      <ToolbarButtons />
+    </div>
+  )
+}
+
+const ToolbarMenus = () => {
+  const { t } = useTranslation()
+  return (
+    <div className="ide-redesign-toolbar-menu">
+      <div className="ide-redesign-toolbar-home-button">
+        <a href="/project" className="ide-redesign-toolbar-home-link">
+          <img
+            className="toolbar-ol-logo"
+            src="/img/ol-brand/overleaf-o-dark.svg"
+            alt={t('overleaf_logo')}
+          />
+          <MaterialIcon type="home" className="toolbar-ol-home-button" />
+        </a>
+      </div>
+      <ToolbarMenuBar />
+    </div>
+  )
+}
+
+const ToolbarButtons = () => {
+  const { t } = useTranslation()
+  return (
+    <div className="ide-redesign-toolbar-actions">
+      <OnlineUsers />
+      <div className="ide-redesign-toolbar-button-container">
+        <OLButton
+          variant="link"
+          className="ide-redesign-toolbar-button-subdued"
+          leadingIcon={<MaterialIcon type="history" />}
+        />
+      </div>
+      <div className="ide-redesign-toolbar-button-container">
+        <OLButton
+          variant="link"
+          className="ide-redesign-toolbar-button-subdued"
+          leadingIcon={<MaterialIcon type="send" />}
+        >
+          {t('submit_title')}
+        </OLButton>
+      </div>
+      <div className="ide-redesign-toolbar-button-container">
+        <OLButton
+          variant="primary"
+          leadingIcon={<MaterialIcon type="person_add" />}
+        >
+          {t('share')}
+        </OLButton>
+      </div>
+    </div>
+  )
+}

+ 1 - 0
services/web/frontend/stylesheets/bootstrap-5/pages/all.scss

@@ -10,6 +10,7 @@
 @import 'editor/rail';
 @import 'editor/settings';
 @import 'editor/toolbar';
+@import 'editor/toolbar-redesign';
 @import 'editor/online-users';
 @import 'editor/hotkeys';
 @import 'editor/left-menu';

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

@@ -54,11 +54,11 @@
   height: 100%;
   padding: var(--spacing-02);
   background: var(--ide-rail-background);
+  border-right: 1px solid var(--border-divider);
 }
 
 .ide-rail-content {
   height: 100%;
-  border: 1px solid var(--border-divider);
 }
 
 .ide-rail-tabs-nav {

+ 113 - 0
services/web/frontend/stylesheets/bootstrap-5/pages/editor/toolbar-redesign.scss

@@ -0,0 +1,113 @@
+@use 'sass:math';
+
+:root {
+  --toolbar-home-button-hover-background: var(--neutral-20);
+}
+
+.ide-redesign-toolbar {
+  $toolbar-height: 48px;
+  $home-button-size: 36px;
+  $ol-icon-height: 24px;
+  $home-icon-font-size: 24px;
+
+  border-bottom: 1px solid var(--border-divider);
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  box-sizing: border-box;
+  height: $toolbar-height;
+
+  .ide-redesign-toolbar-menu {
+    display: flex;
+    gap: var(--spacing-05);
+  }
+
+  .ide-redesign-toolbar-home-button {
+    width: $home-button-size;
+    height: $home-button-size;
+    margin: math.div($toolbar-height - $home-button-size, 2);
+  }
+
+  .ide-redesign-toolbar-home-link {
+    display: flex;
+    height: 100%;
+    width: 100%;
+    justify-content: center;
+    flex-direction: column;
+    text-align: center;
+    border-radius: 100%;
+
+    .toolbar-ol-home-button {
+      display: none;
+      font-size: $home-icon-font-size;
+    }
+
+    .toolbar-ol-logo {
+      margin: 0 auto;
+      height: $ol-icon-height;
+      width: auto;
+    }
+
+    &:hover {
+      background-color: var(--toolbar-home-button-hover-background);
+
+      .toolbar-ol-home-button {
+        display: block;
+      }
+
+      .toolbar-ol-logo {
+        display: none;
+      }
+    }
+  }
+
+  .ide-redesign-toolbar-dropdown-toggle-subdued {
+    $height: 32px;
+
+    border: none;
+    border-radius: var(--border-radius-base);
+    padding: var(--spacing-02);
+    height: $height;
+    font-size: var(--font-size-03);
+    line-height: var(--line-height-03);
+    font-weight: 400;
+    box-sizing: border-box;
+    margin: math.div($toolbar-height - $height, 2) 0;
+
+    &.dropdown-toggle::after {
+      display: none;
+    }
+
+    .material-symbols {
+      vertical-align: middle;
+    }
+  }
+}
+
+.ide-redesign-toolbar-menu-bar {
+  display: flex;
+  justify-content: flex-start;
+  height: 100%;
+  gap: var(--spacing-05);
+}
+
+.ide-redesign-toolbar-actions {
+  display: flex;
+  gap: var(--spacing-06);
+  padding: 0 var(--spacing-05);
+}
+
+.ide-redesign-toolbar-button-container {
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+}
+
+.ide-redesign-toolbar-button-subdued {
+  color: var(--neutral-90);
+  text-decoration: none;
+}
+
+.ide-redesign-online-users {
+  display: flex;
+}

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

@@ -679,6 +679,7 @@
   "features_and_benefits": "Features & Benefits",
   "features_like_track_changes": "Features like real-time track changes",
   "february": "February",
+  "file": "File",
   "file_action_created": "Created",
   "file_action_deleted": "Deleted",
   "file_action_edited": "Edited",
@@ -1454,6 +1455,7 @@
   "overleaf_history_system": "Overleaf History System",
   "overleaf_individual_plans": "Overleaf individual plans",
   "overleaf_labs": "Overleaf Labs",
+  "overleaf_logo": "Overleaf logo",
   "overleaf_plans_and_pricing": "overleaf plans and pricing",
   "overleaf_template_gallery": "overleaf template gallery",
   "overview": "Overview",
@@ -2407,6 +2409,7 @@
   "vat": "VAT",
   "vat_number": "VAT Number",
   "verify_email_address_before_enabling_managed_users": "You need to verify your email address before enabling managed users.",
+  "view": "View",
   "view_all": "View All",
   "view_code": "View code",
   "view_configuration": "View configuration",