mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-06-09 00:59:02 +02:00
Merge pull request #15319 from overleaf/ii-ide-page-prototype-share-modal
Share modal for React IDE page GitOrigin-RevId: f72f824abdcb5a135c354e3ccc35912b2097673f
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
import { useState, useCallback } from 'react'
|
||||
import { useOnlineUsersContext } from '@/features/ide-react/context/online-users-context'
|
||||
import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context'
|
||||
import * as eventTracking from '@/infrastructure/event-tracking'
|
||||
import EditorNavigationToolbarRoot from '@/features/editor-navigation-toolbar/components/editor-navigation-toolbar-root'
|
||||
import ShareProjectModal from '@/features/share-project-modal/components/share-project-modal'
|
||||
|
||||
function EditorNavigationToolbar() {
|
||||
const [showShareModal, setShowShareModal] = useState(false)
|
||||
const { onlineUsersArray } = useOnlineUsersContext()
|
||||
const { openDoc } = useEditorManagerContext()
|
||||
|
||||
const handleOpenShareModal = () => {
|
||||
eventTracking.sendMBOnce('ide-open-share-modal-once')
|
||||
setShowShareModal(true)
|
||||
}
|
||||
|
||||
const handleHideShareModal = useCallback(() => {
|
||||
setShowShareModal(false)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<EditorNavigationToolbarRoot
|
||||
// @ts-ignore
|
||||
onlineUsersArray={onlineUsersArray}
|
||||
openDoc={openDoc}
|
||||
openShareProjectModal={handleOpenShareModal}
|
||||
/>
|
||||
<ShareProjectModal
|
||||
show={showShareModal}
|
||||
handleHide={handleHideShareModal}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default EditorNavigationToolbar
|
||||
@@ -1,63 +0,0 @@
|
||||
import React, { useCallback } from 'react'
|
||||
import ChatToggleButton from '@/features/editor-navigation-toolbar/components/chat-toggle-button'
|
||||
import HistoryToggleButton from '@/features/editor-navigation-toolbar/components/history-toggle-button'
|
||||
import LayoutDropdownButton from '@/features/editor-navigation-toolbar/components/layout-dropdown-button'
|
||||
import MenuButton from '@/features/editor-navigation-toolbar/components/menu-button'
|
||||
import { useLayoutContext } from '@/shared/context/layout-context'
|
||||
import { sendMB } from '@/infrastructure/event-tracking'
|
||||
import OnlineUsersWidget from '@/features/editor-navigation-toolbar/components/online-users-widget'
|
||||
import { useOnlineUsersContext } from '@/features/ide-react/context/online-users-context'
|
||||
|
||||
type HeaderProps = {
|
||||
chatIsOpen: boolean
|
||||
setChatIsOpen: (chatIsOpen: boolean) => void
|
||||
historyIsOpen: boolean
|
||||
setHistoryIsOpen: (historyIsOpen: boolean) => void
|
||||
}
|
||||
|
||||
export default function Header({
|
||||
chatIsOpen,
|
||||
setChatIsOpen,
|
||||
historyIsOpen,
|
||||
setHistoryIsOpen,
|
||||
}: HeaderProps) {
|
||||
const { setLeftMenuShown } = useLayoutContext()
|
||||
const { onlineUsersArray } = useOnlineUsersContext()
|
||||
|
||||
function toggleChatOpen() {
|
||||
setChatIsOpen(!chatIsOpen)
|
||||
}
|
||||
|
||||
function toggleHistoryOpen() {
|
||||
setHistoryIsOpen(!historyIsOpen)
|
||||
}
|
||||
|
||||
const handleShowLeftMenuClick = useCallback(() => {
|
||||
sendMB('navigation-clicked-menu')
|
||||
setLeftMenuShown(value => !value)
|
||||
}, [setLeftMenuShown])
|
||||
|
||||
return (
|
||||
<header className="toolbar toolbar-header">
|
||||
<div className="toolbar-left">
|
||||
<MenuButton onClick={handleShowLeftMenuClick} />
|
||||
</div>
|
||||
<div className="toolbar-right">
|
||||
<OnlineUsersWidget
|
||||
onlineUsers={onlineUsersArray}
|
||||
goToUser={() => alert('Not implemented')}
|
||||
/>
|
||||
<LayoutDropdownButton />
|
||||
<HistoryToggleButton
|
||||
historyIsOpen={historyIsOpen}
|
||||
onClick={toggleHistoryOpen}
|
||||
/>
|
||||
<ChatToggleButton
|
||||
chatIsOpen={chatIsOpen}
|
||||
onClick={toggleChatOpen}
|
||||
unreadMessageCount={0}
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
@@ -7,14 +7,16 @@ import PlaceholderChat from '@/features/ide-react/components/layout/placeholder/
|
||||
import PlaceholderHistory from '@/features/ide-react/components/layout/placeholder/placeholder-history'
|
||||
import MainLayout from '@/features/ide-react/components/layout/main-layout'
|
||||
import { EditorAndSidebar } from '@/features/ide-react/components/editor-and-sidebar'
|
||||
import Header from '@/features/ide-react/components/header'
|
||||
import EditorLeftMenu from '@/features/editor-left-menu/components/editor-left-menu'
|
||||
import EditorNavigationToolbar from '@/features/ide-react/components/editor-navigation-toolbar'
|
||||
import { useLayoutEventTracking } from '@/features/ide-react/hooks/use-layout-event-tracking'
|
||||
import useSocketListeners from '@/features/ide-react/hooks/use-socket-listeners'
|
||||
|
||||
// This is filled with placeholder content while the real content is migrated
|
||||
// away from Angular
|
||||
export default function IdePage() {
|
||||
useLayoutEventTracking()
|
||||
useSocketListeners()
|
||||
|
||||
const [leftColumnDefaultSize, setLeftColumnDefaultSize] = useState(20)
|
||||
const { registerUserActivity } = useConnectionContext()
|
||||
@@ -32,24 +34,8 @@ export default function IdePage() {
|
||||
return () => document.body.removeEventListener('click', listener)
|
||||
}, [listener])
|
||||
|
||||
const { chatIsOpen, setChatIsOpen, view, setView } = useLayoutContext()
|
||||
const { chatIsOpen, view } = useLayoutContext()
|
||||
const historyIsOpen = view === 'history'
|
||||
const setHistoryIsOpen = useCallback(
|
||||
(historyIsOpen: boolean) => {
|
||||
setView(historyIsOpen ? 'history' : 'editor')
|
||||
},
|
||||
[setView]
|
||||
)
|
||||
|
||||
const headerContent = (
|
||||
<Header
|
||||
chatIsOpen={chatIsOpen}
|
||||
setChatIsOpen={setChatIsOpen}
|
||||
historyIsOpen={historyIsOpen}
|
||||
setHistoryIsOpen={setHistoryIsOpen}
|
||||
/>
|
||||
)
|
||||
const chatContent = <PlaceholderChat />
|
||||
|
||||
const mainContent = historyIsOpen ? (
|
||||
<PlaceholderHistory
|
||||
@@ -70,8 +56,8 @@ export default function IdePage() {
|
||||
<Alerts />
|
||||
<EditorLeftMenu />
|
||||
<MainLayout
|
||||
headerContent={headerContent}
|
||||
chatContent={chatContent}
|
||||
headerContent={<EditorNavigationToolbar />}
|
||||
chatContent={<PlaceholderChat />}
|
||||
mainContent={mainContent}
|
||||
chatIsOpen={chatIsOpen}
|
||||
shouldPersistLayout
|
||||
|
||||
+5
-1
@@ -1,5 +1,6 @@
|
||||
import React from 'react'
|
||||
import ChatToggleButton from '@/features/editor-navigation-toolbar/components/chat-toggle-button'
|
||||
import ShareProjectButton from '@/features/editor-navigation-toolbar/components/share-project-button'
|
||||
import HistoryToggleButton from '@/features/editor-navigation-toolbar/components/history-toggle-button'
|
||||
import LayoutDropdownButton from '@/features/editor-navigation-toolbar/components/layout-dropdown-button'
|
||||
|
||||
@@ -16,6 +17,8 @@ export default function PlaceholderHeader({
|
||||
historyIsOpen,
|
||||
setHistoryIsOpen,
|
||||
}: PlaceholderHeaderProps) {
|
||||
function handleOpenShareModal() {}
|
||||
|
||||
function toggleChatOpen() {
|
||||
setChatIsOpen(!chatIsOpen)
|
||||
}
|
||||
@@ -28,11 +31,12 @@ export default function PlaceholderHeader({
|
||||
<header className="toolbar toolbar-header">
|
||||
<div className="toolbar-left">Header placeholder</div>
|
||||
<div className="toolbar-right">
|
||||
<LayoutDropdownButton />
|
||||
<ShareProjectButton onClick={handleOpenShareModal} />
|
||||
<HistoryToggleButton
|
||||
historyIsOpen={historyIsOpen}
|
||||
onClick={toggleHistoryOpen}
|
||||
/>
|
||||
<LayoutDropdownButton />
|
||||
<ChatToggleButton
|
||||
chatIsOpen={chatIsOpen}
|
||||
onClick={toggleChatOpen}
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Modal } from 'react-bootstrap'
|
||||
import AccessibleModal from '@/shared/components/accessible-modal'
|
||||
|
||||
export type GenericMessageModalOwnProps = {
|
||||
title: string
|
||||
message: string
|
||||
}
|
||||
|
||||
type GenericMessageModalProps = React.ComponentProps<typeof AccessibleModal> &
|
||||
GenericMessageModalOwnProps
|
||||
|
||||
function GenericMessageModal({
|
||||
title,
|
||||
message,
|
||||
...modalProps
|
||||
}: GenericMessageModalProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<AccessibleModal {...modalProps}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>{title}</Modal.Title>
|
||||
</Modal.Header>
|
||||
|
||||
<Modal.Body className="modal-body-share">{message}</Modal.Body>
|
||||
|
||||
<Modal.Footer>
|
||||
<button className="btn btn-info" onClick={() => modalProps.onHide()}>
|
||||
{t('ok')}
|
||||
</button>
|
||||
</Modal.Footer>
|
||||
</AccessibleModal>
|
||||
)
|
||||
}
|
||||
|
||||
export default GenericMessageModal
|
||||
@@ -18,7 +18,6 @@ import { JoinProjectPayload } from '@/features/ide-react/connection/join-project
|
||||
import { useConnectionContext } from '@/features/ide-react/context/connection-context'
|
||||
import { getMockIde } from '@/shared/context/mock/mock-ide'
|
||||
import { populateEditorScope } from '@/features/ide-react/context/editor-manager-context'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
import { postJSON } from '@/infrastructure/fetch-json'
|
||||
import { EventLog } from '@/features/ide-react/editor/event-log'
|
||||
import { populateSettingsScope } from '@/features/ide-react/scope-adapters/settings-adapter'
|
||||
@@ -41,10 +40,6 @@ const IdeReactContext = createContext<IdeReactContextValue | undefined>(
|
||||
undefined
|
||||
)
|
||||
|
||||
function showGenericMessageModal(title: string, message: string) {
|
||||
debugConsole.log('*** showGenericMessageModal ***', title, message)
|
||||
}
|
||||
|
||||
function populateIdeReactScope(store: ReactScopeValueStore) {
|
||||
store.set('sync_tex_error', false)
|
||||
store.set('settings', window.userSettings)
|
||||
@@ -156,7 +151,6 @@ export const IdeReactProvider: FC = ({ children }) => {
|
||||
return {
|
||||
...getMockIde(),
|
||||
socket,
|
||||
showGenericMessageModal,
|
||||
reportError,
|
||||
// TODO: MIGRATION: Remove this once it's no longer used
|
||||
fileTreeManager: {
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import {
|
||||
createContext,
|
||||
useContext,
|
||||
FC,
|
||||
useCallback,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import GenericMessageModal, {
|
||||
GenericMessageModalOwnProps,
|
||||
} from '@/features/ide-react/components/modals/generic-message-modal'
|
||||
|
||||
type ModalsContextValue = {
|
||||
showGenericMessageModal: (
|
||||
title: GenericMessageModalOwnProps['title'],
|
||||
message: GenericMessageModalOwnProps['message']
|
||||
) => void
|
||||
}
|
||||
|
||||
const ModalsContext = createContext<ModalsContextValue | undefined>(undefined)
|
||||
|
||||
export const ModalsContextProvider: FC = ({ children }) => {
|
||||
const [showGenericModal, setShowGenericModal] = useState(false)
|
||||
const [genericMessageModalData, setGenericMessageModalData] =
|
||||
useState<GenericMessageModalOwnProps>({ title: '', message: '' })
|
||||
|
||||
const handleHideGenericModal = useCallback(() => {
|
||||
setShowGenericModal(false)
|
||||
}, [])
|
||||
|
||||
const showGenericMessageModal = useCallback(
|
||||
(
|
||||
title: GenericMessageModalOwnProps['title'],
|
||||
message: GenericMessageModalOwnProps['message']
|
||||
) => {
|
||||
setGenericMessageModalData({ title, message })
|
||||
setShowGenericModal(true)
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
const value = useMemo<ModalsContextValue>(
|
||||
() => ({
|
||||
showGenericMessageModal,
|
||||
}),
|
||||
[showGenericMessageModal]
|
||||
)
|
||||
|
||||
return (
|
||||
<ModalsContext.Provider value={value}>
|
||||
{children}
|
||||
<GenericMessageModal
|
||||
show={showGenericModal}
|
||||
onHide={handleHideGenericModal}
|
||||
{...genericMessageModalData}
|
||||
/>
|
||||
</ModalsContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useModalsContext(): ModalsContextValue {
|
||||
const context = useContext(ModalsContext)
|
||||
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
'useModalsContext is only available inside ModalsContextProvider'
|
||||
)
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import { OnlineUsersProvider } from '@/features/ide-react/context/online-users-c
|
||||
import { MetadataProvider } from '@/features/ide-react/context/metadata-context'
|
||||
import { ReferencesProvider } from '@/features/ide-react/context/references-context'
|
||||
import { SplitTestProvider } from '@/shared/context/split-test-context'
|
||||
import { ModalsContextProvider } from '@/features/ide-react/context/modals-context'
|
||||
import { FileTreePathProvider } from '@/features/file-tree/contexts/file-tree-path'
|
||||
|
||||
export const ReactContextRoot: FC = ({ children }) => {
|
||||
@@ -38,7 +39,9 @@ export const ReactContextRoot: FC = ({ children }) => {
|
||||
<EditorManagerProvider>
|
||||
<OnlineUsersProvider>
|
||||
<MetadataProvider>
|
||||
{children}
|
||||
<ModalsContextProvider>
|
||||
{children}
|
||||
</ModalsContextProvider>
|
||||
</MetadataProvider>
|
||||
</OnlineUsersProvider>
|
||||
</EditorManagerProvider>
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import useSocketListener from '@/features/ide-react/hooks/use-socket-listener'
|
||||
import {
|
||||
listProjectInvites,
|
||||
listProjectMembers,
|
||||
} from '@/features/share-project-modal/utils/api'
|
||||
import useScopeValue from '@/shared/hooks/use-scope-value'
|
||||
import { useConnectionContext } from '@/features/ide-react/context/connection-context'
|
||||
import { useIdeReactContext } from '@/features/ide-react/context/ide-react-context'
|
||||
import { useModalsContext } from '@/features/ide-react/context/modals-context'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
|
||||
function useSocketListeners() {
|
||||
const { t } = useTranslation()
|
||||
const { socket } = useConnectionContext()
|
||||
const { projectId } = useIdeReactContext()
|
||||
const { showGenericMessageModal } = useModalsContext()
|
||||
const [, setPublicAccessLevel] = useScopeValue('project.publicAccesLevel')
|
||||
const [, setProjectMembers] = useScopeValue('project.members')
|
||||
const [, setProjectInvites] = useScopeValue('project.invites')
|
||||
|
||||
useSocketListener(socket, 'project:access:revoked', () => {
|
||||
showGenericMessageModal(
|
||||
t('removed_from_project'),
|
||||
t(
|
||||
'you_have_been_removed_from_this_project_and_will_be_redirected_to_project_dashboard'
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
useSocketListener(socket, 'project:publicAccessLevel:changed', data => {
|
||||
if (data.newAccessLevel) {
|
||||
setPublicAccessLevel(data.newAccessLevel)
|
||||
}
|
||||
})
|
||||
|
||||
useSocketListener(socket, 'project:membership:changed', data => {
|
||||
if (data.members) {
|
||||
listProjectMembers(projectId)
|
||||
.then(({ members }) => {
|
||||
if (members) {
|
||||
setProjectMembers(members)
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
debugConsole.error('Error fetching members for project', err)
|
||||
})
|
||||
}
|
||||
|
||||
if (data.invites) {
|
||||
listProjectInvites(projectId)
|
||||
.then(({ invites }) => {
|
||||
if (invites) {
|
||||
setProjectInvites(invites)
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
debugConsole.error('Error fetching invites for project', err)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export default useSocketListeners
|
||||
Reference in New Issue
Block a user