diff --git a/services/web/app/src/Features/Project/ProjectController.js b/services/web/app/src/Features/Project/ProjectController.js index 89b219ebe9..d2864183be 100644 --- a/services/web/app/src/Features/Project/ProjectController.js +++ b/services/web/app/src/Features/Project/ProjectController.js @@ -355,6 +355,7 @@ const _ProjectController = { 'editor-redesign', 'paywall-change-compile-timeout', 'overleaf-assist-bundle', + 'wf-feature-rebrand', ].filter(Boolean) const getUserValues = async userId => diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index 77457ed74d..5fd381e30d 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -405,6 +405,7 @@ "discount_of": "", "discover_the_fastest_way_to_search_and_cite": "", "dismiss_error_popup": "", + "display": "", "display_deleted_user": "", "display_math": "", "do_you_need_edit_access": "", @@ -608,6 +609,7 @@ "full_doc_history": "", "full_width": "", "future_payments": "", + "generate_from_text_or_image": "", "generate_token": "", "generic_if_problem_continues_contact_us": "", "generic_linked_file_compile_error": "", @@ -789,6 +791,7 @@ "include_the_error_message_and_ai_response": "", "increase_indent": "", "increased_compile_timeout": "", + "inline": "", "inline_math": "", "inr_discount_modal_info": "", "inr_discount_modal_title": "", @@ -1483,6 +1486,7 @@ "select_image_from_project_files": "", "select_project": "", "select_projects": "", + "select_size": "", "select_tag": "", "select_user": "", "selected": "", @@ -1789,6 +1793,8 @@ "toolbar_editor": "", "toolbar_format_bold": "", "toolbar_format_italic": "", + "toolbar_generate_math": "", + "toolbar_generate_table": "", "toolbar_increase_indent": "", "toolbar_insert_citation": "", "toolbar_insert_cross_reference": "", @@ -1798,6 +1804,7 @@ "toolbar_insert_link": "", "toolbar_insert_math": "", "toolbar_insert_math_and_symbols": "", + "toolbar_insert_math_lowercase": "", "toolbar_insert_misc": "", "toolbar_insert_table": "", "toolbar_list_indentation": "", diff --git a/services/web/frontend/js/features/source-editor/components/toolbar/math-dropdown.tsx b/services/web/frontend/js/features/source-editor/components/toolbar/math-dropdown.tsx index 0cead93358..710f42c07e 100644 --- a/services/web/frontend/js/features/source-editor/components/toolbar/math-dropdown.tsx +++ b/services/web/frontend/js/features/source-editor/components/toolbar/math-dropdown.tsx @@ -1,25 +1,59 @@ +import { DropdownHeader } from '@/features/ui/components/bootstrap-5/dropdown-menu' import { ToolbarButtonMenu } from './button-menu' import { emitToolbarEvent } from '../../extensions/toolbar/utils/analytics' import MaterialIcon from '../../../../shared/components/material-icon' import { useTranslation } from 'react-i18next' import { useCodeMirrorViewContext } from '../codemirror-context' +import { useEditorContext } from '@/shared/context/editor-context' import { wrapInDisplayMath, wrapInInlineMath, } from '../../extensions/toolbar/commands' import { memo } from 'react' import OLListGroupItem from '@/features/ui/components/ol/ol-list-group-item' +import sparkleWhite from '@/shared/svgs/sparkle-small-white.svg' +import sparkle from '@/shared/svgs/ai-sparkle-text.svg' +import { isSplitTestEnabled } from '@/utils/splitTestUtils' export const MathDropdown = memo(function MathDropdown() { const { t } = useTranslation() const view = useCodeMirrorViewContext() + const { writefullInstance } = useEditorContext() + const wfRebrandEnabled = isSplitTestEnabled('wf-feature-rebrand') return ( } > + {wfRebrandEnabled && writefullInstance && ( + <> + + {t('toolbar_insert_math_lowercase')} + + { + writefullInstance?.openEquationGenerator() + }} + > + + + {t('generate_from_text_or_image')} + + + )} { @@ -30,7 +64,7 @@ export const MathDropdown = memo(function MathDropdown() { }} > - {t('toolbar_insert_inline_math')} + {t('inline')} - {t('toolbar_insert_display_math')} + {t('display')} ) diff --git a/services/web/frontend/js/features/source-editor/components/toolbar/table-dropdown.tsx b/services/web/frontend/js/features/source-editor/components/toolbar/table-dropdown.tsx new file mode 100644 index 0000000000..a98c78584a --- /dev/null +++ b/services/web/frontend/js/features/source-editor/components/toolbar/table-dropdown.tsx @@ -0,0 +1,101 @@ +import { DropdownHeader } from '@/features/ui/components/bootstrap-5/dropdown-menu' +import { ToolbarButtonMenu } from './button-menu' +import MaterialIcon from '../../../../shared/components/material-icon' +import { useTranslation } from 'react-i18next' +import { useEditorContext } from '@/shared/context/editor-context' +import { memo, useRef, useCallback } from 'react' +import OLListGroupItem from '@/features/ui/components/ol/ol-list-group-item' +import sparkleWhite from '@/shared/svgs/sparkle-small-white.svg' +import sparkle from '@/shared/svgs/ai-sparkle-text.svg' +import { TableInserterDropdown } from './table-inserter-dropdown' +import OLOverlay from '@/features/ui/components/ol/ol-overlay' +import OLPopover from '@/features/ui/components/ol/ol-popover' +import useDropdown from '../../../../shared/hooks/use-dropdown' +import * as commands from '../../extensions/toolbar/commands' +import { useCodeMirrorViewContext } from '../codemirror-context' +import { emitToolbarEvent } from '../../extensions/toolbar/utils/analytics' + +export const TableDropdown = memo(function TableDropdown() { + const { t } = useTranslation() + const { writefullInstance } = useEditorContext() + const { open, onToggle, ref } = useDropdown() + const target = useRef(null) + const view = useCodeMirrorViewContext() + + const onSizeSelected = useCallback( + (sizeX: number, sizeY: number) => { + onToggle(false) + commands.insertTable(view, sizeX, sizeY) + emitToolbarEvent(view, 'table-generator-insert-table') + view.focus() + }, + [view, onToggle] + ) + + return ( + <> +
+ } + > + + {t('toolbar_table_insert_table_lowercase')} + + { + writefullInstance?.openTableGenerator() + }} + > + + + {t('generate_from_text_or_image')} + +
+ { + event.preventDefault() + event.stopPropagation() + }} + onClick={() => { + onToggle(!open) + }} + > + {t('select_size')} + + + onToggle(false)} + > + + + + +
+ + ) +}) diff --git a/services/web/frontend/js/features/source-editor/components/toolbar/table-inserter-dropdown-legacy.tsx b/services/web/frontend/js/features/source-editor/components/toolbar/table-inserter-dropdown-legacy.tsx new file mode 100644 index 0000000000..7ab12f4a77 --- /dev/null +++ b/services/web/frontend/js/features/source-editor/components/toolbar/table-inserter-dropdown-legacy.tsx @@ -0,0 +1,128 @@ +import { FC, memo, useCallback, useRef, useState } from 'react' +import * as commands from '../../extensions/toolbar/commands' +import { useTranslation } from 'react-i18next' +import useDropdown from '../../../../shared/hooks/use-dropdown' +import { useCodeMirrorViewContext } from '../codemirror-context' +import OLTooltip from '@/features/ui/components/ol/ol-tooltip' +import OLOverlay from '@/features/ui/components/ol/ol-overlay' +import OLPopover from '@/features/ui/components/ol/ol-popover' +import MaterialIcon from '../../../../shared/components/material-icon' +import classNames from 'classnames' +import { emitToolbarEvent } from '../../extensions/toolbar/utils/analytics' + +export const LegacyTableDropdown = memo(() => { + const { t } = useTranslation() + const { open, onToggle, ref } = useDropdown() + const view = useCodeMirrorViewContext() + const target = useRef(null) + + const onSizeSelected = useCallback( + (sizeX: number, sizeY: number) => { + onToggle(false) + commands.insertTable(view, sizeX, sizeY) + emitToolbarEvent(view, 'table-generator-insert-table') + view.focus() + }, + [view, onToggle] + ) + + return ( + <> +
} + overlayProps={{ placement: 'bottom' }} + > + + + onToggle(false)} + > + +
+ +
+
+
+ + ) +}) +LegacyTableDropdown.displayName = 'TableInserterDropdown' + +const range = (start: number, end: number) => + Array.from({ length: end - start + 1 }, (v, k) => k + start) + +const SizeGrid: FC<{ + sizeX: number + sizeY: number + onSizeSelected: (sizeX: number, sizeY: number) => void +}> = ({ sizeX, sizeY, onSizeSelected }) => { + const [currentSize, setCurrentSize] = useState<{ + sizeX: number + sizeY: number + }>({ sizeX: 0, sizeY: 0 }) + const { t } = useTranslation() + let label = t('toolbar_table_insert_table_lowercase') + if (currentSize.sizeX > 0 && currentSize.sizeY > 0) { + label = t('toolbar_table_insert_size_table', { + size: `${currentSize.sizeY}×${currentSize.sizeX}`, + }) + } + return ( + <> +
{label}
+ { + setCurrentSize({ sizeX: 0, sizeY: 0 }) + }} + > + + {range(1, sizeY).map(y => ( + + {range(1, sizeX).map(x => ( + // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions + + ))} + +
= x && currentSize.sizeY >= y, + })} + key={x} + onMouseEnter={() => { + setCurrentSize({ sizeX: x, sizeY: y }) + }} + onMouseUp={() => onSizeSelected(x, y)} + /> + ))} +
+ + ) +} diff --git a/services/web/frontend/js/features/source-editor/components/toolbar/table-inserter-dropdown.tsx b/services/web/frontend/js/features/source-editor/components/toolbar/table-inserter-dropdown.tsx index 1385f9ba98..f9f81d518a 100644 --- a/services/web/frontend/js/features/source-editor/components/toolbar/table-inserter-dropdown.tsx +++ b/services/web/frontend/js/features/source-editor/components/toolbar/table-inserter-dropdown.tsx @@ -1,78 +1,18 @@ -import { FC, memo, useCallback, useRef, useState } from 'react' -import * as commands from '../../extensions/toolbar/commands' -import { useTranslation } from 'react-i18next' -import useDropdown from '../../../../shared/hooks/use-dropdown' -import { useCodeMirrorViewContext } from '../codemirror-context' -import OLTooltip from '@/features/ui/components/ol/ol-tooltip' -import OLOverlay from '@/features/ui/components/ol/ol-overlay' -import OLPopover from '@/features/ui/components/ol/ol-popover' -import MaterialIcon from '../../../../shared/components/material-icon' +import { FC, useState } from 'react' import classNames from 'classnames' -import { emitToolbarEvent } from '../../extensions/toolbar/utils/analytics' - -export const TableInserterDropdown = memo(() => { - const { t } = useTranslation() - const { open, onToggle, ref } = useDropdown() - const view = useCodeMirrorViewContext() - const target = useRef(null) - - const onSizeSelected = useCallback( - (sizeX: number, sizeY: number) => { - onToggle(false) - commands.insertTable(view, sizeX, sizeY) - emitToolbarEvent(view, 'table-generator-insert-table') - view.focus() - }, - [view, onToggle] - ) +import { useTranslation } from 'react-i18next' +export const TableInserterDropdown = ({ + onSizeSelected, +}: { + onSizeSelected: (sizeX: number, sizeY: number) => void +}) => { return ( - <> - - onToggle(false)} - > - -
- -
-
-
- +
+ +
) -}) +} TableInserterDropdown.displayName = 'TableInserterDropdown' const range = (start: number, end: number) => diff --git a/services/web/frontend/js/features/source-editor/components/toolbar/toolbar-items.tsx b/services/web/frontend/js/features/source-editor/components/toolbar/toolbar-items.tsx index b313bf6534..6a9509b642 100644 --- a/services/web/frontend/js/features/source-editor/components/toolbar/toolbar-items.tsx +++ b/services/web/frontend/js/features/source-editor/components/toolbar/toolbar-items.tsx @@ -9,8 +9,10 @@ import getMeta from '../../../../utils/meta' import { InsertFigureDropdown } from './insert-figure-dropdown' import { useTranslation } from 'react-i18next' import { MathDropdown } from './math-dropdown' -import { TableInserterDropdown } from './table-inserter-dropdown' +import { TableDropdown } from './table-dropdown' +import { LegacyTableDropdown } from './table-inserter-dropdown-legacy' import { withinFormattingCommand } from '@/features/source-editor/utils/tree-operations/formatting' +import { isSplitTestEnabled } from '@/utils/splitTestUtils' import { isMac } from '@/shared/utils/os' export const ToolbarItems: FC<{ @@ -27,12 +29,15 @@ export const ToolbarItems: FC<{ listDepth, }) { const { t } = useTranslation() - const { toggleSymbolPalette, showSymbolPalette } = useEditorContext() + const { toggleSymbolPalette, showSymbolPalette, writefullInstance } = + useEditorContext() const isActive = withinFormattingCommand(state) const symbolPaletteAvailable = getMeta('ol-symbolPaletteAvailable') const showGroup = (group: string) => !overflowed || overflowed.has(group) + const wfRebrandEnabled = isSplitTestEnabled('wf-feature-rebrand') + return ( <> {showGroup('group-history') && ( @@ -142,7 +147,11 @@ export const ToolbarItems: FC<{ icon="book_5" /> - + {wfRebrandEnabled && writefullInstance ? ( + + ) : ( + + )} )} {showGroup('group-list') && ( diff --git a/services/web/frontend/js/features/source-editor/extensions/toolbar/toolbar-panel.ts b/services/web/frontend/js/features/source-editor/extensions/toolbar/toolbar-panel.ts index e09d9d56d3..a2fd37473f 100644 --- a/services/web/frontend/js/features/source-editor/extensions/toolbar/toolbar-panel.ts +++ b/services/web/frontend/js/features/source-editor/extensions/toolbar/toolbar-panel.ts @@ -59,6 +59,26 @@ const toolbarTheme = EditorView.theme({ }, }, }, + '.ol-cm-toolbar-header': { + color: 'var(--toolbar-btn-color)', + }, + '.ol-cm-toolbar-dropdown-divider': { + borderBottom: '1px solid', + borderColor: 'var(--toolbar-dropdown-divider-color)', + }, + // here render both the icons, and hide one depending on if its dark or light mode with &.overall-theme-dark + '.ol-cm-toolbar-ai-sparkle-gradient': { + display: 'block', + }, + '.ol-cm-toolbar-ai-sparkle-white': { + display: 'none', + }, + '&.overall-theme-dark .ol-cm-toolbar-ai-sparkle-gradient': { + display: 'none', + }, + '&.overall-theme-dark .ol-cm-toolbar-ai-sparkle-white': { + display: 'block', + }, '.ol-cm-toolbar-button-menu-popover': { backgroundColor: 'initial', '& > .popover-content, & > .popover-body': { diff --git a/services/web/frontend/js/shared/context/types/writefull-instance.ts b/services/web/frontend/js/shared/context/types/writefull-instance.ts index 8dd0023f07..7b34d978ec 100644 --- a/services/web/frontend/js/shared/context/types/writefull-instance.ts +++ b/services/web/frontend/js/shared/context/types/writefull-instance.ts @@ -28,4 +28,6 @@ export interface WritefullAPI { name: eventName, callback: (detail: WritefullEvents[eventName]) => void ): void + openTableGenerator(): void + openEquationGenerator(): void } diff --git a/services/web/frontend/js/shared/svgs/sparkle-small-white.svg b/services/web/frontend/js/shared/svgs/sparkle-small-white.svg new file mode 100644 index 0000000000..343a85d778 --- /dev/null +++ b/services/web/frontend/js/shared/svgs/sparkle-small-white.svg @@ -0,0 +1 @@ + diff --git a/services/web/frontend/stylesheets/bootstrap-5/foundations/colors.scss b/services/web/frontend/stylesheets/bootstrap-5/foundations/colors.scss index 84a169b0eb..73e93273c4 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/foundations/colors.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/foundations/colors.scss @@ -199,6 +199,7 @@ $link-ui-visited-dark: $blue-40; --border-active: var(--blue-50); --border-danger: var(--red-50); --border-divider: var(--neutral-20); + --border-dark-divider: var(--neutral-70); --link-web: var(--green-50); --link-web-hover: var(--green-60); --link-web-visited: var(--green-50); diff --git a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/toolbar.scss b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/toolbar.scss index edf523d56a..d5b3958566 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/toolbar.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/toolbar.scss @@ -22,6 +22,7 @@ --editor-header-logo-background: url(../../../../../public/img/ol-brand/overleaf-o-white.svg) center / contain no-repeat; --editor-toolbar-bg: var(--neutral-80); + --toolbar-dropdown-divider-color: var(--border-dark-divider); } .ide-redesign-main { @@ -70,6 +71,7 @@ --editor-header-logo-background: url(../../../../../public/img/ol-brand/overleaf-o.svg) center / contain no-repeat; --editor-toolbar-bg: var(--white); + --toolbar-dropdown-divider-color: var(--border-divider); .ide-redesign-main { --toolbar-alt-bg-color: var(--bg-light-secondary); diff --git a/services/web/locales/en.json b/services/web/locales/en.json index ed8b473dda..bb71b78fa5 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -529,6 +529,7 @@ "discover_the_fastest_way_to_search_and_cite": "Discover the fastest way to search and cite", "discover_why_over_people_worldwide_trust_overleaf": "Discover why over __count__ million people worldwide trust Overleaf with their work.", "dismiss_error_popup": "Dismiss first error alert", + "display": "Display", "display_deleted_user": "Display deleted users", "display_math": "Display math", "do_not_have_acct_or_do_not_want_to_link": "If you don’t have an __appName__ account, or if you don’t want to link to your __institutionName__ account, please click __clickText__.", @@ -807,6 +808,7 @@ "gallery_page_title": "Gallery - Templates, Examples and Articles written in LaTeX", "gallery_show_more_tags": "Show more", "general": "General", + "generate_from_text_or_image": "From text or image", "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.", @@ -1019,6 +1021,7 @@ "increased_compile_timeout": "Increased compile timeout", "individuals": "Individuals", "info": "Info", + "inline": "Inline", "inline_math": "Inline math", "inr_discount_modal_info": "Get document history, track changes, additional collaborators, and more at Purchasing Power Parity prices.", "inr_discount_modal_title": "70% off all Overleaf premium plans for users in India", @@ -1948,6 +1951,7 @@ "select_image_from_project_files": "Select image from project files", "select_project": "Select __project__", "select_projects": "Select Projects", + "select_size": "Select size", "select_tag": "Select tag __tagName__", "select_user": "Select user", "selected": "Selected", @@ -2316,6 +2320,8 @@ "toolbar_editor": "Editor tools", "toolbar_format_bold": "Format Bold", "toolbar_format_italic": "Format Italic", + "toolbar_generate_math": "Generate Math", + "toolbar_generate_table": "Generate Table", "toolbar_increase_indent": "Increase Indent", "toolbar_insert_citation": "Insert Citation", "toolbar_insert_cross_reference": "Insert Cross-reference", @@ -2325,6 +2331,7 @@ "toolbar_insert_link": "Insert Link", "toolbar_insert_math": "Insert Math", "toolbar_insert_math_and_symbols": "Insert Math and Symbols", + "toolbar_insert_math_lowercase": "Insert math", "toolbar_insert_misc": "Insert Misc (links, citations, cross-references, figures, tables)", "toolbar_insert_table": "Insert Table", "toolbar_list_indentation": "List and Indentation",