diff --git a/services/web/app/src/Features/Project/ProjectController.js b/services/web/app/src/Features/Project/ProjectController.js index 1592411f18..2571318b16 100644 --- a/services/web/app/src/Features/Project/ProjectController.js +++ b/services/web/app/src/Features/Project/ProjectController.js @@ -833,6 +833,8 @@ const ProjectController = { wsUrl, showSupport: Features.hasFeature('support'), showNewLogsUI: user.alphaProgram && !wantsOldLogsUI, + showNewNavigationUI: + req.query && req.query.new_navigation_ui === 'true', showReactFileTree: user.betaProgram && !wantsOldFileTreeUI }) timer.done() diff --git a/services/web/app/views/project/editor.pug b/services/web/app/views/project/editor.pug index dd12741d73..99412ae198 100644 --- a/services/web/app/views/project/editor.pug +++ b/services/web/app/views/project/editor.pug @@ -8,7 +8,10 @@ block vars block content .editor(ng-controller="IdeController").full-size //- required by react2angular-shared-context, must be rendered as a top level component - shared-context-react() + div( + ng-controller="ReactRootContextController" + ) + shared-context-react(editor-loading="editorLoading") .loading-screen(ng-if="state.loading") .loading-screen-brand-container .loading-screen-brand( @@ -74,7 +77,10 @@ block content ng-cloak ) .ui-layout-center - include ./editor/header + if showNewNavigationUI + include ./editor/header-react + else + include ./editor/header include ./editor/share != moduleIncludes("publish:body", locals) diff --git a/services/web/app/views/project/editor/header-react.pug b/services/web/app/views/project/editor/header-react.pug new file mode 100644 index 0000000000..9760222246 --- /dev/null +++ b/services/web/app/views/project/editor/header-react.pug @@ -0,0 +1,3 @@ +div(ng-controller="EditorNavigationToolbarController") + + editor-navigation-toolbar-root(on-show-left-menu-click="onShowLeftMenuClick") \ No newline at end of file diff --git a/services/web/frontend/extracted-translation-keys.json b/services/web/frontend/extracted-translation-keys.json index 0967203055..3249197ee0 100644 --- a/services/web/frontend/extracted-translation-keys.json +++ b/services/web/frontend/extracted-translation-keys.json @@ -5,6 +5,7 @@ "autocompile_disabled_reason", "autocomplete", "autocomplete_references", + "back_to_your_projects", "blocked_filename", "cancel", "clear_cached_files", @@ -67,6 +68,7 @@ "main_file_not_found", "math_display", "math_inline", + "menu", "n_errors", "n_errors_plural", "n_items", diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/components/back-to-projects-button.js b/services/web/frontend/js/features/editor-navigation-toolbar/components/back-to-projects-button.js new file mode 100644 index 0000000000..6a3a26d2af --- /dev/null +++ b/services/web/frontend/js/features/editor-navigation-toolbar/components/back-to-projects-button.js @@ -0,0 +1,18 @@ +import React from 'react' +import Icon from '../../../shared/components/icon' +import { useTranslation } from 'react-i18next' + +function BackToProjectsButton() { + const { t } = useTranslation() + return ( + + + + ) +} + +export default BackToProjectsButton diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/components/cobranding-logo.js b/services/web/frontend/js/features/editor-navigation-toolbar/components/cobranding-logo.js new file mode 100644 index 0000000000..279c403bbf --- /dev/null +++ b/services/web/frontend/js/features/editor-navigation-toolbar/components/cobranding-logo.js @@ -0,0 +1,31 @@ +import React from 'react' +import PropTypes from 'prop-types' + +function CobrandingLogo({ + brandVariationHomeUrl, + brandVariationName, + logoImgUrl +}) { + return ( + + {brandVariationName} + + ) +} + +CobrandingLogo.propTypes = { + brandVariationHomeUrl: PropTypes.string.isRequired, + brandVariationName: PropTypes.string.isRequired, + logoImgUrl: PropTypes.string.isRequired +} + +export default CobrandingLogo diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/components/editor-navigation-toolbar-root.js b/services/web/frontend/js/features/editor-navigation-toolbar/components/editor-navigation-toolbar-root.js new file mode 100644 index 0000000000..6ed5c30238 --- /dev/null +++ b/services/web/frontend/js/features/editor-navigation-toolbar/components/editor-navigation-toolbar-root.js @@ -0,0 +1,23 @@ +import React from 'react' +import PropTypes from 'prop-types' +import ToolbarHeader from './toolbar-header' +import { useEditorContext } from '../../../shared/context/editor-context' + +function EditorNavigationToolbarRoot({ onShowLeftMenuClick }) { + const { cobranding, loading } = useEditorContext() + + // using {display: 'none'} as 1:1 migration from Angular's ng-hide. Using + // `loading ? null : ` causes UI glitches + return ( + + ) +} + +EditorNavigationToolbarRoot.propTypes = { + onShowLeftMenuClick: PropTypes.func.isRequired +} +export default EditorNavigationToolbarRoot diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/components/menu-button.js b/services/web/frontend/js/features/editor-navigation-toolbar/components/menu-button.js new file mode 100644 index 0000000000..ed852db80f --- /dev/null +++ b/services/web/frontend/js/features/editor-navigation-toolbar/components/menu-button.js @@ -0,0 +1,22 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { useTranslation } from 'react-i18next' +import Icon from '../../../shared/components/icon' + +function MenuButton({ onClick }) { + const { t } = useTranslation() + + return ( + // eslint-disable-next-line jsx-a11y/anchor-is-valid + + +

{t('menu')}

+
+ ) +} + +MenuButton.propTypes = { + onClick: PropTypes.func.isRequired +} + +export default MenuButton diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/components/toolbar-header.js b/services/web/frontend/js/features/editor-navigation-toolbar/components/toolbar-header.js new file mode 100644 index 0000000000..340e2046d0 --- /dev/null +++ b/services/web/frontend/js/features/editor-navigation-toolbar/components/toolbar-header.js @@ -0,0 +1,24 @@ +import React from 'react' +import PropTypes from 'prop-types' +import MenuButton from './menu-button' +import CobrandingLogo from './cobranding-logo' +import BackToProjectsButton from './back-to-projects-button' + +function ToolbarHeader({ cobranding, onShowLeftMenuClick }) { + return ( +
+
+ + {cobranding ? : null} + +
+
+ ) +} + +ToolbarHeader.propTypes = { + onShowLeftMenuClick: PropTypes.func.isRequired, + cobranding: PropTypes.object +} + +export default ToolbarHeader diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/controllers/editor-navigation-toolbar-controller.js b/services/web/frontend/js/features/editor-navigation-toolbar/controllers/editor-navigation-toolbar-controller.js new file mode 100644 index 0000000000..bc385b60ab --- /dev/null +++ b/services/web/frontend/js/features/editor-navigation-toolbar/controllers/editor-navigation-toolbar-controller.js @@ -0,0 +1,18 @@ +import App from '../../../base' +import { react2angular } from 'react2angular' +import EditorNavigationToolbarRoot from '../components/editor-navigation-toolbar-root' +import { rootContext } from '../../../shared/context/root-context' + +App.controller('EditorNavigationToolbarController', function($scope, ide) { + $scope.onShowLeftMenuClick = () => + ide.$scope.$applyAsync(() => { + ide.$scope.ui.leftMenuShown = !ide.$scope.ui.leftMenuShown + }) +}) + +App.component( + 'editorNavigationToolbarRoot', + react2angular(rootContext.use(EditorNavigationToolbarRoot), [ + 'onShowLeftMenuClick' + ]) +) diff --git a/services/web/frontend/js/ide.js b/services/web/frontend/js/ide.js index 0728abe840..5e0a267d2e 100644 --- a/services/web/frontend/js/ide.js +++ b/services/web/frontend/js/ide.js @@ -61,9 +61,8 @@ import './main/account-upgrade-angular' import './main/exposed-settings-angular' import './main/system-messages' import '../../modules/modules-ide.js' - -import { react2angular } from 'react2angular' -import { rootContext } from './shared/context/root-context' +import './shared/context/controllers/root-context-controller' +import './features/editor-navigation-toolbar/controllers/editor-navigation-toolbar-controller' App.controller('IdeController', function( $scope, @@ -353,10 +352,6 @@ If the project has been renamed please look in your project list for a new proje }) }) -// required by react2angular-shared-context, maps the shared context instance to an angular component -// that must be rendered in the app -App.component('sharedContextReact', react2angular(rootContext.component)) - export default angular.bootstrap(document.body, ['SharelatexApp']) function __guard__(value, transform) { diff --git a/services/web/frontend/js/shared/context/controllers/root-context-controller.js b/services/web/frontend/js/shared/context/controllers/root-context-controller.js new file mode 100644 index 0000000000..8125b3aac2 --- /dev/null +++ b/services/web/frontend/js/shared/context/controllers/root-context-controller.js @@ -0,0 +1,15 @@ +import App from '../../../base' +import { react2angular } from 'react2angular' +import { rootContext } from '../root-context' + +App.controller('ReactRootContextController', function($scope, ide) { + $scope.editorLoading = !!ide.$scope.state.loading + ide.$scope.$watch('state.loading', editorLoading => { + $scope.editorLoading = editorLoading + }) +}) + +App.component( + 'sharedContextReact', + react2angular(rootContext.component, ['editorLoading']) +) diff --git a/services/web/frontend/js/shared/context/editor-context.js b/services/web/frontend/js/shared/context/editor-context.js index 41ca0091b4..5f5f8abad8 100644 --- a/services/web/frontend/js/shared/context/editor-context.js +++ b/services/web/frontend/js/shared/context/editor-context.js @@ -3,13 +3,23 @@ import PropTypes from 'prop-types' export const EditorContext = createContext() -export function EditorProvider({ children }) { +export function EditorProvider({ children, loading }) { + const cobranding = window.brandVariation + ? { + logoImgUrl: window.brandVariation.logo_url, + brandVariationName: window.brandVariation.name, + brandVariationHomeUrl: window.brandVariation.home_url + } + : undefined + const ownerId = window._ide.$scope.project && window._ide.$scope.project.owner ? window._ide.$scope.project.owner._id : null const editorContextValue = { + cobranding, + loading, projectId: window.project_id, isProjectOwner: ownerId === window.user.id } @@ -22,7 +32,8 @@ export function EditorProvider({ children }) { } EditorProvider.propTypes = { - children: PropTypes.any + children: PropTypes.any, + loading: PropTypes.bool } export function useEditorContext() { diff --git a/services/web/frontend/js/shared/context/root-context.js b/services/web/frontend/js/shared/context/root-context.js index f06186f972..18ae2a7e0b 100644 --- a/services/web/frontend/js/shared/context/root-context.js +++ b/services/web/frontend/js/shared/context/root-context.js @@ -1,15 +1,20 @@ import React from 'react' +import PropTypes from 'prop-types' import { ApplicationProvider } from './application-context' import { EditorProvider } from './editor-context' import createSharedContext from 'react2angular-shared-context' -// eslint-disable-next-line react/prop-types -export function ContextRoot({ children }) { +export function ContextRoot({ children, editorLoading }) { return ( - {children} + {children} ) } +ContextRoot.propTypes = { + children: PropTypes.any, + editorLoading: PropTypes.bool +} + export const rootContext = createSharedContext(ContextRoot) diff --git a/services/web/frontend/stories/editor-navigation-toolbar.stories.js b/services/web/frontend/stories/editor-navigation-toolbar.stories.js new file mode 100644 index 0000000000..c40b67b73a --- /dev/null +++ b/services/web/frontend/stories/editor-navigation-toolbar.stories.js @@ -0,0 +1,10 @@ +import React from 'react' +import ToolbarHeader from '../js/features/editor-navigation-toolbar/components/toolbar-header' + +export const Default = () => { + return +} + +export default { + title: 'EditorNavigationToolbar' +}