diff --git a/services/web/app/views/project/editor.pug b/services/web/app/views/project/editor.pug
index 834a6a4fc3..62fd40479c 100644
--- a/services/web/app/views/project/editor.pug
+++ b/services/web/app/views/project/editor.pug
@@ -8,16 +8,7 @@ block vars
block content
.editor(ng-controller="IdeController").full-size
//- required by react2angular-shared-context, must be rendered as a top level component
- div(
- ng-controller="ReactRootContextController"
- )
- shared-context-react(
- editor-loading="editorLoading"
- chat-is-open-angular="chatIsOpenAngular"
- set-chat-is-open-angular="setChatIsOpenAngular"
- open-doc="openDoc"
- online-users-array="onlineUsersArray"
- )
+ shared-context-react()
.loading-screen(ng-if="state.loading")
.loading-screen-brand-container
.loading-screen-brand(
@@ -123,7 +114,7 @@ block content
aside.chat(
ng-controller="ReactChatController"
)
- chat(reset-unread-messages="resetUnreadMessages" chat-is-open="chatIsOpen")
+ chat()
script(type="text/ng-template", id="genericMessageModalTemplate")
.modal-header
diff --git a/services/web/app/views/project/editor/header-react.pug b/services/web/app/views/project/editor/header-react.pug
index 9760222246..1ff99d2d93 100644
--- a/services/web/app/views/project/editor/header-react.pug
+++ b/services/web/app/views/project/editor/header-react.pug
@@ -1,3 +1,6 @@
div(ng-controller="EditorNavigationToolbarController")
- editor-navigation-toolbar-root(on-show-left-menu-click="onShowLeftMenuClick")
\ No newline at end of file
+ editor-navigation-toolbar-root(
+ open-doc="openDoc"
+ online-users-array="onlineUsersArray"
+ )
\ No newline at end of file
diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json
index f288d2c84d..cdf57f3f8a 100644
--- a/services/web/frontend/extracted-translations.json
+++ b/services/web/frontend/extracted-translations.json
@@ -54,6 +54,7 @@
"go_to_error_location": "",
"headers": "",
"hide_outline": "",
+ "history": "",
"hotkeys": "",
"ignore_validation_errors": "",
"invalid_file_name": "",
diff --git a/services/web/frontend/js/features/chat/components/chat-pane.js b/services/web/frontend/js/features/chat/components/chat-pane.js
index ad529dda24..51bd7d2526 100644
--- a/services/web/frontend/js/features/chat/components/chat-pane.js
+++ b/services/web/frontend/js/features/chat/components/chat-pane.js
@@ -1,19 +1,18 @@
import React, { useEffect, useState } from 'react'
+import PropTypes from 'prop-types'
import MessageList from './message-list'
import MessageInput from './message-input'
import InfiniteScroll from './infinite-scroll'
import Icon from '../../../shared/components/icon'
import { useTranslation } from 'react-i18next'
-import { useEditorContext } from '../../../shared/context/editor-context'
+import { useLayoutContext } from '../../../shared/context/layout-context'
import withErrorBoundary from '../../../infrastructure/error-boundary'
import { useChatContext } from '../context/chat-context'
function ChatPane() {
const { t } = useTranslation()
- const {
- ui: { chatIsOpen }
- } = useEditorContext()
+ const { chatIsOpen } = useLayoutContext({ chatIsOpen: PropTypes.bool })
const {
userId,
diff --git a/services/web/frontend/js/features/chat/context/chat-context.js b/services/web/frontend/js/features/chat/context/chat-context.js
index 30f173f595..1211658077 100644
--- a/services/web/frontend/js/features/chat/context/chat-context.js
+++ b/services/web/frontend/js/features/chat/context/chat-context.js
@@ -10,20 +10,37 @@ import { useApplicationContext } from '../../../shared/context/application-conte
import { useEditorContext } from '../../../shared/context/editor-context'
import { ChatStore } from '../store/chat-store'
import useBrowserWindow from '../../../infrastructure/browser-window-hook'
+import { useLayoutContext } from '../../../shared/context/layout-context'
export const ChatContext = createContext()
+ChatContext.Provider.propTypes = {
+ value: PropTypes.shape({
+ userId: PropTypes.string.isRequired,
+ atEnd: PropTypes.bool,
+ loading: PropTypes.bool,
+ messages: PropTypes.array.isRequired,
+ unreadMessageCount: PropTypes.number.isRequired,
+ resetUnreadMessageCount: PropTypes.func.isRequired,
+ loadMoreMessages: PropTypes.func.isRequired,
+ sendMessage: PropTypes.func.isRequired
+ }).isRequired
+}
+
export function ChatProvider({ children }) {
const {
hasFocus: windowHasFocus,
flashTitle,
stopFlashingTitle
} = useBrowserWindow()
- const { user } = useApplicationContext()
- const {
- projectId,
- ui: { chatIsOpen }
- } = useEditorContext()
+ const { user } = useApplicationContext({
+ user: PropTypes.shape({ id: PropTypes.string.isRequired }.isRequired)
+ })
+ const { projectId } = useEditorContext({
+ projectId: PropTypes.string.isRequired
+ })
+
+ const { chatIsOpen } = useLayoutContext({ chatIsOpen: PropTypes.bool })
const [unreadMessageCount, setUnreadMessageCount] = useState(0)
function resetUnreadMessageCount() {
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
index a44e1f5e99..0844ee34d1 100644
--- 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
@@ -3,23 +3,61 @@ import PropTypes from 'prop-types'
import ToolbarHeader from './toolbar-header'
import { useEditorContext } from '../../../shared/context/editor-context'
import { useChatContext } from '../../chat/context/chat-context'
+import { useLayoutContext } from '../../../shared/context/layout-context'
+
+const editorContextPropTypes = {
+ cobranding: PropTypes.object,
+ loading: PropTypes.bool,
+ isRestrictedTokenMember: PropTypes.bool
+}
+
+const layoutContextPropTypes = {
+ chatIsOpen: PropTypes.bool,
+ setChatIsOpen: PropTypes.func.isRequired,
+ reviewPanelOpen: PropTypes.bool,
+ setReviewPanelOpen: PropTypes.func.isRequired,
+ view: PropTypes.string,
+ setView: PropTypes.func.isRequired,
+ setLeftMenuShown: PropTypes.func.isRequired
+}
+
+function EditorNavigationToolbarRoot({ onlineUsersArray, openDoc }) {
+ const { cobranding, loading, isRestrictedTokenMember } = useEditorContext(
+ editorContextPropTypes
+ )
-function EditorNavigationToolbarRoot({ onShowLeftMenuClick }) {
const {
- cobranding,
- loading,
- ui,
- onlineUsersArray,
- openDoc
- } = useEditorContext()
+ chatIsOpen,
+ setChatIsOpen,
+ reviewPanelOpen,
+ setReviewPanelOpen,
+ view,
+ setView,
+ setLeftMenuShown
+ } = useLayoutContext(layoutContextPropTypes)
+
const { resetUnreadMessageCount, unreadMessageCount } = useChatContext()
const toggleChatOpen = useCallback(() => {
- if (!ui.chatIsOpen) {
+ if (!chatIsOpen) {
resetUnreadMessageCount()
}
- ui.toggleChatOpen()
- }, [ui, resetUnreadMessageCount])
+ setChatIsOpen(value => !value)
+ }, [chatIsOpen, setChatIsOpen, resetUnreadMessageCount])
+
+ const toggleReviewPanelOpen = useCallback(
+ () => setReviewPanelOpen(value => !value),
+ [setReviewPanelOpen]
+ )
+
+ const toggleHistoryOpen = useCallback(() => {
+ setView(view === 'history' ? 'editor' : 'history')
+ }, [view, setView])
+
+ const onShowLeftMenuClick = useCallback(
+ () => setLeftMenuShown(value => !value),
+ [setLeftMenuShown]
+ )
function goToUser(user) {
if (user.doc && typeof user.row === 'number') {
@@ -34,16 +72,23 @@ function EditorNavigationToolbarRoot({ onShowLeftMenuClick }) {
style={loading ? { display: 'none' } : {}}
cobranding={cobranding}
onShowLeftMenuClick={onShowLeftMenuClick}
- chatIsOpen={ui.chatIsOpen}
+ chatIsOpen={chatIsOpen}
unreadMessageCount={unreadMessageCount}
toggleChatOpen={toggleChatOpen}
+ reviewPanelOpen={reviewPanelOpen}
+ toggleReviewPanelOpen={toggleReviewPanelOpen}
+ historyIsOpen={view === 'history'}
+ toggleHistoryOpen={toggleHistoryOpen}
onlineUsers={onlineUsersArray}
goToUser={goToUser}
+ isRestrictedTokenMember={isRestrictedTokenMember}
/>
)
}
EditorNavigationToolbarRoot.propTypes = {
- onShowLeftMenuClick: PropTypes.func.isRequired
+ onlineUsersArray: PropTypes.array.isRequired,
+ openDoc: PropTypes.func.isRequired
}
+
export default EditorNavigationToolbarRoot
diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/components/history-toggle-button.js b/services/web/frontend/js/features/editor-navigation-toolbar/components/history-toggle-button.js
new file mode 100644
index 0000000000..6bc79ccc33
--- /dev/null
+++ b/services/web/frontend/js/features/editor-navigation-toolbar/components/history-toggle-button.js
@@ -0,0 +1,28 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import classNames from 'classnames'
+import { useTranslation } from 'react-i18next'
+import Icon from '../../../shared/components/icon'
+
+function HistoryToggleButton({ historyIsOpen, onClick }) {
+ const { t } = useTranslation()
+
+ const classes = classNames('btn', 'btn-full-height', {
+ active: historyIsOpen
+ })
+
+ return (
+ // eslint-disable-next-line jsx-a11y/anchor-is-valid
+
+
+ {t('history')}
+
+ )
+}
+
+HistoryToggleButton.propTypes = {
+ historyIsOpen: PropTypes.bool,
+ onClick: PropTypes.func.isRequired
+}
+
+export default HistoryToggleButton
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
index 99ab4650c9..f08c7601dc 100644
--- 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
@@ -5,15 +5,22 @@ import CobrandingLogo from './cobranding-logo'
import BackToProjectsButton from './back-to-projects-button'
import ChatToggleButton from './chat-toggle-button'
import OnlineUsersWidget from './online-users-widget'
+import TrackChangesToggleButton from './track-changes-toggle-button'
+import HistoryToggleButton from './history-toggle-button'
function ToolbarHeader({
cobranding,
onShowLeftMenuClick,
chatIsOpen,
toggleChatOpen,
+ reviewPanelOpen,
+ toggleReviewPanelOpen,
+ historyIsOpen,
+ toggleHistoryOpen,
unreadMessageCount,
onlineUsers,
- goToUser
+ goToUser,
+ isRestrictedTokenMember
}) {
return (
)
@@ -39,9 +59,14 @@ ToolbarHeader.propTypes = {
cobranding: PropTypes.object,
chatIsOpen: PropTypes.bool,
toggleChatOpen: PropTypes.func.isRequired,
+ reviewPanelOpen: PropTypes.bool,
+ toggleReviewPanelOpen: PropTypes.func.isRequired,
+ historyIsOpen: PropTypes.bool,
+ toggleHistoryOpen: PropTypes.func.isRequired,
unreadMessageCount: PropTypes.number.isRequired,
onlineUsers: PropTypes.array.isRequired,
- goToUser: PropTypes.func.isRequired
+ goToUser: PropTypes.func.isRequired,
+ isRestrictedTokenMember: PropTypes.bool
}
export default ToolbarHeader
diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/components/track-changes-toggle-button.js b/services/web/frontend/js/features/editor-navigation-toolbar/components/track-changes-toggle-button.js
new file mode 100644
index 0000000000..edd36f471f
--- /dev/null
+++ b/services/web/frontend/js/features/editor-navigation-toolbar/components/track-changes-toggle-button.js
@@ -0,0 +1,34 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import classNames from 'classnames'
+import { useTranslation } from 'react-i18next'
+
+function TrackChangesToggleButton({ trackChangesIsOpen, disabled, onClick }) {
+ const { t } = useTranslation()
+ const classes = classNames('btn', 'btn-full-height', {
+ active: trackChangesIsOpen && !disabled,
+ disabled: disabled
+ })
+
+ return (
+ // eslint-disable-next-line jsx-a11y/anchor-is-valid
+
+
+ {t('review')}
+
+ )
+}
+
+TrackChangesToggleButton.propTypes = {
+ trackChangesIsOpen: PropTypes.bool,
+ disabled: PropTypes.bool,
+ onClick: PropTypes.func.isRequired
+}
+
+export default TrackChangesToggleButton
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
index bc385b60ab..31a674faaf 100644
--- 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
@@ -4,15 +4,18 @@ import EditorNavigationToolbarRoot from '../components/editor-navigation-toolbar
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
- })
+ // wrapper is required to avoid scope problems with `this` inside `EditorManager`
+ $scope.openDoc = (doc, args) => ide.editorManager.openDoc(doc, args)
})
App.component(
'editorNavigationToolbarRoot',
react2angular(rootContext.use(EditorNavigationToolbarRoot), [
- 'onShowLeftMenuClick'
+ 'openDoc',
+
+ // `$scope.onlineUsersArray` is already populated by `OnlineUsersManager`, which also creates
+ // a new array instance every time the list of online users change (which should refresh the
+ // value passed to React as a prop, triggering a re-render)
+ 'onlineUsersArray'
])
)
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
index 90b17f4b19..19d98e92eb 100644
--- a/services/web/frontend/js/shared/context/controllers/root-context-controller.js
+++ b/services/web/frontend/js/shared/context/controllers/root-context-controller.js
@@ -2,42 +2,7 @@ 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
- })
-
- $scope.setChatIsOpenAngular = value => {
- ide.$scope.$applyAsync(() => {
- ide.$scope.ui.chatOpen = value
- })
- }
-
- // we need to pass `$scope.ui.chatOpen` to Angular while both React and Angular
- // Navigation Toolbars exist in the codebase. Once the Angular version is removed,
- // the React Navigation Toolbar will be the only source of truth for the open/closed state,
- // but `setChatIsOpenAngular` will still need to exist since Angular is responsible of the
- // global layout
- $scope.chatIsOpenAngular = ide.$scope.ui.chatOpen
- ide.$scope.$watch('ui.chatOpen', value => {
- $scope.chatIsOpenAngular = value
- })
-
- // wrapper is required to avoid scope problems with `this` inside `EditorManager`
- $scope.openDoc = (doc, args) => ide.editorManager.openDoc(doc, args)
-})
-
App.component(
'sharedContextReact',
- react2angular(rootContext.component, [
- 'editorLoading',
- 'setChatIsOpenAngular',
- 'chatIsOpenAngular',
- 'openDoc',
- // `$scope.onlineUsersArray` is already populated by `OnlineUsersManager`, which also creates
- // a new array instance every time the list of online users change (which should refresh the
- // value passed to React as a prop, triggering a re-render)
- 'onlineUsersArray'
- ])
+ react2angular(rootContext.component, [], ['ide'])
)
diff --git a/services/web/frontend/js/shared/context/editor-context.js b/services/web/frontend/js/shared/context/editor-context.js
index 988a5a7f2d..4a41ad0cb9 100644
--- a/services/web/frontend/js/shared/context/editor-context.js
+++ b/services/web/frontend/js/shared/context/editor-context.js
@@ -1,6 +1,6 @@
-import React, { createContext, useCallback, useContext, useEffect } from 'react'
+import React, { createContext, useContext } from 'react'
import PropTypes from 'prop-types'
-import usePersistedState from '../../infrastructure/persisted-state-hook'
+import useScopeValue from './util/scope-value-hook'
export const EditorContext = createContext()
@@ -13,18 +13,12 @@ EditorContext.Provider.propTypes = {
}),
loading: PropTypes.bool,
projectId: PropTypes.string.isRequired,
- isProjectOwner: PropTypes.bool
+ isProjectOwner: PropTypes.bool,
+ isRestrictedTokenMember: PropTypes.bool
})
}
-export function EditorProvider({
- children,
- loading,
- chatIsOpenAngular,
- setChatIsOpenAngular,
- openDoc,
- onlineUsersArray
-}) {
+export function EditorProvider({ children, $scope }) {
const cobranding = window.brandVariation
? {
logoImgUrl: window.brandVariation.logo_url,
@@ -38,39 +32,14 @@ export function EditorProvider({
? window._ide.$scope.project.owner._id
: null
- const [chatIsOpen, setChatIsOpen] = usePersistedState(
- 'editor.ui.chat.open',
- false
- )
-
- const toggleChatOpen = useCallback(() => {
- setChatIsOpen(!chatIsOpen)
- setChatIsOpenAngular(!chatIsOpen)
- }, [chatIsOpen, setChatIsOpenAngular, setChatIsOpen])
-
- // updates React's `chatIsOpen` state when the chat is opened by Angular.
- // In order to prevent race conditions with `toggleChatOpen` it's not a 1:1 binding:
- // Angular forces the React state to `true`, but can only set it to `false` when
- // the React state is explicitly `true`.
- useEffect(() => {
- if (chatIsOpenAngular) {
- setChatIsOpen(true)
- } else if (chatIsOpen) {
- setChatIsOpen(false)
- }
- }, [chatIsOpenAngular, chatIsOpen, setChatIsOpen])
+ const [loading] = useScopeValue('state.loading', $scope)
const editorContextValue = {
cobranding,
loading,
projectId: window.project_id,
isProjectOwner: ownerId === window.user.id,
- openDoc,
- onlineUsersArray,
- ui: {
- chatIsOpen,
- toggleChatOpen
- }
+ isRestrictedTokenMember: window.isRestrictedTokenMember
}
return (
@@ -82,11 +51,7 @@ export function EditorProvider({
EditorProvider.propTypes = {
children: PropTypes.any,
- loading: PropTypes.bool,
- chatIsOpenAngular: PropTypes.bool,
- setChatIsOpenAngular: PropTypes.func.isRequired,
- openDoc: PropTypes.func.isRequired,
- onlineUsersArray: PropTypes.array.isRequired
+ $scope: PropTypes.any.isRequired
}
export function useEditorContext(propTypes) {
diff --git a/services/web/frontend/js/shared/context/layout-context.js b/services/web/frontend/js/shared/context/layout-context.js
new file mode 100644
index 0000000000..29e33cc4c5
--- /dev/null
+++ b/services/web/frontend/js/shared/context/layout-context.js
@@ -0,0 +1,59 @@
+import React, { createContext, useContext } from 'react'
+import PropTypes from 'prop-types'
+import useScopeValue from './util/scope-value-hook'
+
+export const LayoutContext = createContext()
+
+LayoutContext.Provider.propTypes = {
+ value: PropTypes.shape({
+ view: PropTypes.string,
+ setView: PropTypes.func.isRequired,
+ chatIsOpen: PropTypes.bool,
+ setChatIsOpen: PropTypes.func.isRequired,
+ reviewPanelOpen: PropTypes.bool,
+ setReviewPanelOpen: PropTypes.func.isRequired,
+ leftMenuShown: PropTypes.bool,
+ setLeftMenuShown: PropTypes.func.isRequired
+ }).isRequired
+}
+
+export function LayoutProvider({ children, $scope }) {
+ const [view, setView] = useScopeValue('ui.view', $scope)
+ const [chatIsOpen, setChatIsOpen] = useScopeValue('ui.chatOpen', $scope)
+ const [reviewPanelOpen, setReviewPanelOpen] = useScopeValue(
+ 'ui.reviewPanelOpen',
+ $scope
+ )
+ const [leftMenuShown, setLeftMenuShown] = useScopeValue(
+ 'ui.leftMenuShown',
+ $scope
+ )
+
+ const layoutContextValue = {
+ view,
+ setView,
+ chatIsOpen,
+ setChatIsOpen,
+ reviewPanelOpen,
+ setReviewPanelOpen,
+ leftMenuShown,
+ setLeftMenuShown
+ }
+
+ return (
+
+ {children}
+
+ )
+}
+
+LayoutProvider.propTypes = {
+ children: PropTypes.any,
+ $scope: PropTypes.any.isRequired
+}
+
+export function useLayoutContext(propTypes) {
+ const data = useContext(LayoutContext)
+ PropTypes.checkPropTypes(propTypes, data, 'data', 'LayoutContext.Provider')
+ return data
+}
diff --git a/services/web/frontend/js/shared/context/root-context.js b/services/web/frontend/js/shared/context/root-context.js
index ae91ba783b..892d40297f 100644
--- a/services/web/frontend/js/shared/context/root-context.js
+++ b/services/web/frontend/js/shared/context/root-context.js
@@ -4,25 +4,15 @@ import { ApplicationProvider } from './application-context'
import { EditorProvider } from './editor-context'
import createSharedContext from 'react2angular-shared-context'
import { ChatProvider } from '../../features/chat/context/chat-context'
+import { LayoutProvider } from './layout-context'
-export function ContextRoot({
- children,
- editorLoading,
- chatIsOpenAngular,
- setChatIsOpenAngular,
- openDoc,
- onlineUsersArray
-}) {
+export function ContextRoot({ children, ide }) {
return (
-
- {children}
+
+
+ {children}
+
)
@@ -30,11 +20,7 @@ export function ContextRoot({
ContextRoot.propTypes = {
children: PropTypes.any,
- editorLoading: PropTypes.bool,
- chatIsOpenAngular: PropTypes.bool,
- setChatIsOpenAngular: PropTypes.func.isRequired,
- openDoc: PropTypes.func.isRequired,
- onlineUsersArray: PropTypes.array.isRequired
+ ide: PropTypes.any.isRequired
}
export const rootContext = createSharedContext(ContextRoot)
diff --git a/services/web/frontend/js/shared/context/util/scope-value-hook.js b/services/web/frontend/js/shared/context/util/scope-value-hook.js
new file mode 100644
index 0000000000..4c233ab6d1
--- /dev/null
+++ b/services/web/frontend/js/shared/context/util/scope-value-hook.js
@@ -0,0 +1,38 @@
+import { useCallback, useEffect, useState } from 'react'
+import _ from 'lodash'
+
+/**
+ * Binds a property in an Angular scope making it accessible in a React
+ * component. The interface is compatible with React.useState(), including
+ * the option of passing a function to the setter.
+ *
+ * @param {string} path - dot '.' path of a property in `sourceScope`.
+ * @param {object} $scope - Angular $scope containing the value to bind.
+ * @returns {[any, function]} - Binded value and setter function tuple.
+ */
+export default function useScopeValue(path, $scope, deep = false) {
+ const [value, setValue] = useState(() => _.get($scope, path))
+
+ useEffect(() => {
+ return $scope.$watch(
+ path,
+ newValue => {
+ setValue(newValue)
+ },
+ deep
+ )
+ }, [path, $scope, deep])
+
+ const scopeSetter = useCallback(
+ newValue => {
+ setValue(val => {
+ const actualNewValue = _.isFunction(newValue) ? newValue(val) : newValue
+ $scope.$applyAsync(() => _.set($scope, path, actualNewValue))
+ return actualNewValue
+ })
+ },
+ [path, $scope]
+ )
+
+ return [value, scopeSetter]
+}
diff --git a/services/web/test/frontend/helpers/render-with-context.js b/services/web/test/frontend/helpers/render-with-context.js
index a551deab75..ad7901bbc4 100644
--- a/services/web/test/frontend/helpers/render-with-context.js
+++ b/services/web/test/frontend/helpers/render-with-context.js
@@ -4,6 +4,7 @@ import { ApplicationProvider } from '../../../frontend/js/shared/context/applica
import { EditorProvider } from '../../../frontend/js/shared/context/editor-context'
import sinon from 'sinon'
import { ChatProvider } from '../../../frontend/js/features/chat/context/chat-context'
+import { LayoutProvider } from '../../../frontend/js/shared/context/layout-context'
export function renderWithEditorContext(
children,
@@ -17,7 +18,11 @@ export function renderWithEditorContext(
owner: {
_id: '124abd'
}
- }
+ },
+ ui: {
+ chatOpen: true
+ },
+ $watch: () => {}
},
socket: {
on: sinon.stub(),
@@ -27,19 +32,17 @@ export function renderWithEditorContext(
return render(
{}}
- setChatIsOpenAngular={() => {}}
openDoc={() => {}}
onlineUsersArray={[]}
+ $scope={window._ide.$scope}
>
- {children}
+ {children}
)
}
export function renderWithChatContext(children, { user, projectId } = {}) {
- global.localStorage.setItem('editor.ui.chat.open', true)
return renderWithEditorContext({children}, {
user,
projectId