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 (
+
+
+
+ )
+}
+
+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 (
+
+ )
+}
+
+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'
+}