mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #15592 from overleaf/td-ide-page-more-modals
React IDE page: add out-of-sync modal and project deleted modal GitOrigin-RevId: 6081a40c3009e8ebc5b3f9752c4059af3d52966d
This commit is contained in:
parent
ecf7befe88
commit
c3afce73c1
13 changed files with 257 additions and 25 deletions
|
@ -466,6 +466,7 @@
|
||||||
"helps_us_tailor_your_experience": "",
|
"helps_us_tailor_your_experience": "",
|
||||||
"hide_configuration": "",
|
"hide_configuration": "",
|
||||||
"hide_document_preamble": "",
|
"hide_document_preamble": "",
|
||||||
|
"hide_local_file_contents": "",
|
||||||
"hide_outline": "",
|
"hide_outline": "",
|
||||||
"history": "",
|
"history": "",
|
||||||
"history_add_label": "",
|
"history_add_label": "",
|
||||||
|
@ -790,6 +791,8 @@
|
||||||
"organize_projects": "",
|
"organize_projects": "",
|
||||||
"other_logs_and_files": "",
|
"other_logs_and_files": "",
|
||||||
"other_output_files": "",
|
"other_output_files": "",
|
||||||
|
"out_of_sync": "",
|
||||||
|
"out_of_sync_detail": "",
|
||||||
"output_file": "",
|
"output_file": "",
|
||||||
"overall_theme": "",
|
"overall_theme": "",
|
||||||
"overleaf": "",
|
"overleaf": "",
|
||||||
|
@ -843,6 +846,7 @@
|
||||||
"please_select_a_project": "",
|
"please_select_a_project": "",
|
||||||
"please_select_an_output_file": "",
|
"please_select_an_output_file": "",
|
||||||
"please_set_main_file": "",
|
"please_set_main_file": "",
|
||||||
|
"please_wait": "",
|
||||||
"plus_additional_collaborators_document_history_track_changes_and_more": "",
|
"plus_additional_collaborators_document_history_track_changes_and_more": "",
|
||||||
"plus_more": "",
|
"plus_more": "",
|
||||||
"plus_upgraded_accounts_receive": "",
|
"plus_upgraded_accounts_receive": "",
|
||||||
|
@ -875,6 +879,8 @@
|
||||||
"project_not_linked_to_github": "",
|
"project_not_linked_to_github": "",
|
||||||
"project_ownership_transfer_confirmation_1": "",
|
"project_ownership_transfer_confirmation_1": "",
|
||||||
"project_ownership_transfer_confirmation_2": "",
|
"project_ownership_transfer_confirmation_2": "",
|
||||||
|
"project_renamed_or_deleted": "",
|
||||||
|
"project_renamed_or_deleted_detail": "",
|
||||||
"project_synced_with_git_repo_at": "",
|
"project_synced_with_git_repo_at": "",
|
||||||
"project_synchronisation": "",
|
"project_synchronisation": "",
|
||||||
"project_timed_out_enable_stop_on_first_error": "",
|
"project_timed_out_enable_stop_on_first_error": "",
|
||||||
|
@ -936,6 +942,7 @@
|
||||||
"reject": "",
|
"reject": "",
|
||||||
"reject_all": "",
|
"reject_all": "",
|
||||||
"relink_your_account": "",
|
"relink_your_account": "",
|
||||||
|
"reload_editor": "",
|
||||||
"remote_service_error": "",
|
"remote_service_error": "",
|
||||||
"remove": "",
|
"remove": "",
|
||||||
"remove_collaborator": "",
|
"remove_collaborator": "",
|
||||||
|
@ -1068,6 +1075,7 @@
|
||||||
"show_in_code": "",
|
"show_in_code": "",
|
||||||
"show_in_pdf": "",
|
"show_in_pdf": "",
|
||||||
"show_less": "",
|
"show_less": "",
|
||||||
|
"show_local_file_contents": "",
|
||||||
"show_outline": "",
|
"show_outline": "",
|
||||||
"show_x_more": "",
|
"show_x_more": "",
|
||||||
"show_x_more_projects": "",
|
"show_x_more_projects": "",
|
||||||
|
@ -1362,6 +1370,7 @@
|
||||||
"welcome_to_sl": "",
|
"welcome_to_sl": "",
|
||||||
"were_in_the_process_of_reducing_compile_timeout_which_may_affect_this_project": "",
|
"were_in_the_process_of_reducing_compile_timeout_which_may_affect_this_project": "",
|
||||||
"were_in_the_process_of_reducing_compile_timeout_which_may_affect_your_project": "",
|
"were_in_the_process_of_reducing_compile_timeout_which_may_affect_your_project": "",
|
||||||
|
"were_performing_maintenance": "",
|
||||||
"weve_recently_reduced_the_compile_timeout_limit_which_may_have_affected_this_project": "",
|
"weve_recently_reduced_the_compile_timeout_limit_which_may_have_affected_this_project": "",
|
||||||
"weve_recently_reduced_the_compile_timeout_limit_which_may_have_affected_your_project": "",
|
"weve_recently_reduced_the_compile_timeout_limit_which_may_have_affected_your_project": "",
|
||||||
"what_does_this_mean": "",
|
"what_does_this_mean": "",
|
||||||
|
|
|
@ -21,7 +21,7 @@ export function Alerts() {
|
||||||
return (
|
return (
|
||||||
<div className="global-alerts">
|
<div className="global-alerts">
|
||||||
{connectionState.forceDisconnected ? (
|
{connectionState.forceDisconnected ? (
|
||||||
<Alert bsStyle="warning" className="small">
|
<Alert bsStyle="danger" className="small">
|
||||||
<strong>{t('disconnected')}</strong>
|
<strong>{t('disconnected')}</strong>
|
||||||
</Alert>
|
</Alert>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import EditorNavigationToolbar from '@/features/ide-react/components/editor-navi
|
||||||
import ChatPane from '@/features/chat/components/chat-pane'
|
import ChatPane from '@/features/chat/components/chat-pane'
|
||||||
import { useLayoutEventTracking } from '@/features/ide-react/hooks/use-layout-event-tracking'
|
import { useLayoutEventTracking } from '@/features/ide-react/hooks/use-layout-event-tracking'
|
||||||
import useSocketListeners from '@/features/ide-react/hooks/use-socket-listeners'
|
import useSocketListeners from '@/features/ide-react/hooks/use-socket-listeners'
|
||||||
|
import { useModalsContext } from '@/features/ide-react/context/modals-context'
|
||||||
import { useOpenFile } from '@/features/ide-react/hooks/use-open-file'
|
import { useOpenFile } from '@/features/ide-react/hooks/use-open-file'
|
||||||
|
|
||||||
// This is filled with placeholder content while the real content is migrated
|
// This is filled with placeholder content while the real content is migrated
|
||||||
|
@ -25,7 +26,19 @@ export default function IdePage() {
|
||||||
useOpenFile()
|
useOpenFile()
|
||||||
|
|
||||||
const [leftColumnDefaultSize, setLeftColumnDefaultSize] = useState(20)
|
const [leftColumnDefaultSize, setLeftColumnDefaultSize] = useState(20)
|
||||||
const { registerUserActivity } = useConnectionContext()
|
const { connectionState, registerUserActivity } = useConnectionContext()
|
||||||
|
const { showLockEditorMessageModal } = useModalsContext()
|
||||||
|
|
||||||
|
// Show modal when editor is forcefully disconnected
|
||||||
|
useEffect(() => {
|
||||||
|
if (connectionState.forceDisconnected) {
|
||||||
|
showLockEditorMessageModal(connectionState.forcedDisconnectDelay)
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
connectionState.forceDisconnected,
|
||||||
|
connectionState.forcedDisconnectDelay,
|
||||||
|
showLockEditorMessageModal,
|
||||||
|
])
|
||||||
|
|
||||||
// Inform the connection manager when the user is active
|
// Inform the connection manager when the user is active
|
||||||
const listener = useCallback(
|
const listener = useCallback(
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { Modal } from 'react-bootstrap'
|
||||||
|
import AccessibleModal from '@/shared/components/accessible-modal'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
export type LockEditorMessageModalProps = {
|
||||||
|
delay: number // In seconds
|
||||||
|
show: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
function LockEditorMessageModal({ delay, show }: LockEditorMessageModalProps) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const [secondsUntilRefresh, setSecondsUntilRefresh] = useState(0)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (show) {
|
||||||
|
setSecondsUntilRefresh(delay)
|
||||||
|
|
||||||
|
const timer = window.setInterval(() => {
|
||||||
|
setSecondsUntilRefresh(seconds => Math.max(0, seconds - 1))
|
||||||
|
}, 1000)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.clearInterval(timer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [show, delay])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AccessibleModal
|
||||||
|
show={show}
|
||||||
|
// It's not possible to hide this modal, but it's a required prop
|
||||||
|
onHide={() => {}}
|
||||||
|
className="lock-editor-modal"
|
||||||
|
backdrop={false}
|
||||||
|
keyboard={false}
|
||||||
|
>
|
||||||
|
<Modal.Header>
|
||||||
|
<Modal.Title>{t('please_wait')}</Modal.Title>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Body>
|
||||||
|
{t('were_performing_maintenance', { seconds: secondsUntilRefresh })}
|
||||||
|
</Modal.Body>
|
||||||
|
</AccessibleModal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LockEditorMessageModal
|
|
@ -0,0 +1,81 @@
|
||||||
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
|
import { Button, Modal } from 'react-bootstrap'
|
||||||
|
import AccessibleModal from '@/shared/components/accessible-modal'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { useLocation } from '@/shared/hooks/use-location'
|
||||||
|
|
||||||
|
export type OutOfSyncModalProps = {
|
||||||
|
editorContent: string
|
||||||
|
show: boolean
|
||||||
|
onHide: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
function OutOfSyncModal({ editorContent, show, onHide }: OutOfSyncModalProps) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const location = useLocation()
|
||||||
|
const [editorContentShown, setEditorContentShown] = useState(false)
|
||||||
|
const editorContentRows = (editorContent.match(/\n/g)?.length || 0) + 1
|
||||||
|
|
||||||
|
// Reload the page to avoid staying in an inconsistent state.
|
||||||
|
// https://github.com/overleaf/issues/issues/3694
|
||||||
|
function done() {
|
||||||
|
onHide()
|
||||||
|
location.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AccessibleModal
|
||||||
|
show={show}
|
||||||
|
onHide={done}
|
||||||
|
className="out-of-sync-modal"
|
||||||
|
backdrop={false}
|
||||||
|
keyboard={false}
|
||||||
|
>
|
||||||
|
<Modal.Header closeButton>
|
||||||
|
<Modal.Title>{t('out_of_sync')}</Modal.Title>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Body className="modal-body-share">
|
||||||
|
<Trans
|
||||||
|
i18nKey="out_of_sync_detail"
|
||||||
|
components={[
|
||||||
|
// eslint-disable-next-line react/jsx-key
|
||||||
|
<br />,
|
||||||
|
// eslint-disable-next-line jsx-a11y/anchor-has-content,react/jsx-key
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
href="/learn/Kb/Editor_out_of_sync_problems"
|
||||||
|
/>,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Modal.Body>
|
||||||
|
<Modal.Body>
|
||||||
|
<Button
|
||||||
|
bsStyle="info"
|
||||||
|
onClick={() => setEditorContentShown(shown => !shown)}
|
||||||
|
>
|
||||||
|
{editorContentShown
|
||||||
|
? t('hide_local_file_contents')
|
||||||
|
: t('show_local_file_contents')}
|
||||||
|
</Button>
|
||||||
|
{editorContentShown ? (
|
||||||
|
<div className="text-preview">
|
||||||
|
<textarea
|
||||||
|
className="scroll-container"
|
||||||
|
readOnly
|
||||||
|
rows={editorContentRows}
|
||||||
|
value={editorContent}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</Modal.Body>
|
||||||
|
<Modal.Footer>
|
||||||
|
<Button bsStyle="info" onClick={done}>
|
||||||
|
{t('reload_editor')}
|
||||||
|
</Button>
|
||||||
|
</Modal.Footer>
|
||||||
|
</AccessibleModal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OutOfSyncModal
|
|
@ -24,6 +24,7 @@ const initialState: ConnectionState = {
|
||||||
inactiveDisconnect: false,
|
inactiveDisconnect: false,
|
||||||
lastConnectionAttempt: 0,
|
lastConnectionAttempt: 0,
|
||||||
reconnectAt: null,
|
reconnectAt: null,
|
||||||
|
forcedDisconnectDelay: 0,
|
||||||
error: '',
|
error: '',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,9 +182,10 @@ export class ConnectionManager extends Emitter<Events> {
|
||||||
this.changeState({
|
this.changeState({
|
||||||
...this.state,
|
...this.state,
|
||||||
forceDisconnected: true,
|
forceDisconnected: true,
|
||||||
|
forcedDisconnectDelay: delay,
|
||||||
error,
|
error,
|
||||||
})
|
})
|
||||||
setTimeout(() => this.disconnect(), delay * 1000)
|
setTimeout(() => this.disconnect(), 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
private onJoinProjectResponse({
|
private onJoinProjectResponse({
|
||||||
|
|
|
@ -13,6 +13,7 @@ export type ConnectionState = {
|
||||||
forceDisconnected: boolean
|
forceDisconnected: boolean
|
||||||
inactiveDisconnect: boolean
|
inactiveDisconnect: boolean
|
||||||
reconnectAt: number | null
|
reconnectAt: number | null
|
||||||
|
forcedDisconnectDelay: number
|
||||||
lastConnectionAttempt: number
|
lastConnectionAttempt: number
|
||||||
error: '' | ConnectionError
|
error: '' | ConnectionError
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { ConnectionState } from '../connection/types/connection-state'
|
||||||
import { ConnectionManager } from '@/features/ide-react/connection/connection-manager'
|
import { ConnectionManager } from '@/features/ide-react/connection/connection-manager'
|
||||||
import { Socket } from '@/features/ide-react/connection/types/socket'
|
import { Socket } from '@/features/ide-react/connection/types/socket'
|
||||||
import { secondsUntil } from '@/features/ide-react/connection/utils'
|
import { secondsUntil } from '@/features/ide-react/connection/utils'
|
||||||
|
import { useLocation } from '@/shared/hooks/use-location'
|
||||||
|
|
||||||
type ConnectionContextValue = {
|
type ConnectionContextValue = {
|
||||||
socket: Socket
|
socket: Socket
|
||||||
|
@ -28,6 +29,8 @@ const ConnectionContext = createContext<ConnectionContextValue | undefined>(
|
||||||
)
|
)
|
||||||
|
|
||||||
export const ConnectionProvider: FC = ({ children }) => {
|
export const ConnectionProvider: FC = ({ children }) => {
|
||||||
|
const location = useLocation()
|
||||||
|
|
||||||
const [connectionManager] = useState(() => new ConnectionManager())
|
const [connectionManager] = useState(() => new ConnectionManager())
|
||||||
const [connectionState, setConnectionState] = useState(
|
const [connectionState, setConnectionState] = useState(
|
||||||
connectionManager.state
|
connectionManager.state
|
||||||
|
@ -69,6 +72,24 @@ export const ConnectionProvider: FC = ({ children }) => {
|
||||||
connectionManager.disconnect()
|
connectionManager.disconnect()
|
||||||
}, [connectionManager])
|
}, [connectionManager])
|
||||||
|
|
||||||
|
// Reload the page on force disconnect. Doing this in React-land means that we
|
||||||
|
// can use useLocation(), which provides mockable location methods
|
||||||
|
useEffect(() => {
|
||||||
|
if (connectionState.forceDisconnected) {
|
||||||
|
const timer = window.setTimeout(
|
||||||
|
() => location.reload(),
|
||||||
|
connectionState.forcedDisconnectDelay * 1000
|
||||||
|
)
|
||||||
|
return () => {
|
||||||
|
window.clearTimeout(timer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
connectionState.forceDisconnected,
|
||||||
|
connectionState.forcedDisconnectDelay,
|
||||||
|
location,
|
||||||
|
])
|
||||||
|
|
||||||
const value = useMemo<ConnectionContextValue>(
|
const value = useMemo<ConnectionContextValue>(
|
||||||
() => ({
|
() => ({
|
||||||
socket: connectionManager.socket,
|
socket: connectionManager.socket,
|
||||||
|
|
|
@ -115,7 +115,8 @@ export const EditorManagerProvider: FC = ({ children }) => {
|
||||||
const { reportError, eventEmitter, eventLog } = useIdeReactContext()
|
const { reportError, eventEmitter, eventLog } = useIdeReactContext()
|
||||||
const { socket, disconnect } = useConnectionContext()
|
const { socket, disconnect } = useConnectionContext()
|
||||||
const { view, setView } = useLayoutContext()
|
const { view, setView } = useLayoutContext()
|
||||||
const { showGenericMessageModal, genericModalVisible } = useModalsContext()
|
const { showGenericMessageModal, genericModalVisible, showOutOfSyncModal } =
|
||||||
|
useModalsContext()
|
||||||
|
|
||||||
const [showSymbolPalette, setShowSymbolPalette] = useScopeValue<boolean>(
|
const [showSymbolPalette, setShowSymbolPalette] = useScopeValue<boolean>(
|
||||||
'editor.showSymbolPalette'
|
'editor.showSymbolPalette'
|
||||||
|
@ -550,13 +551,7 @@ export const EditorManagerProvider: FC = ({ children }) => {
|
||||||
|
|
||||||
// Tell the user about the error state.
|
// Tell the user about the error state.
|
||||||
setIsInErrorState(true)
|
setIsInErrorState(true)
|
||||||
|
showOutOfSyncModal(editorContent || '')
|
||||||
// TODO: MIGRATION: Show out-of-sync modal
|
|
||||||
// this.ide.showOutOfSyncModal(
|
|
||||||
// 'Out of sync',
|
|
||||||
// "Sorry, this file has gone out of sync and we need to do a full refresh. <br> <a target='_blank' rel='noopener noreferrer' href='/learn/Kb/Editor_out_of_sync_problems'>Please see this help guide for more information</a>",
|
|
||||||
// editorContent
|
|
||||||
// )
|
|
||||||
|
|
||||||
// Do not forceReopen the document.
|
// Do not forceReopen the document.
|
||||||
return
|
return
|
||||||
|
@ -581,6 +576,7 @@ export const EditorManagerProvider: FC = ({ children }) => {
|
||||||
reportError,
|
reportError,
|
||||||
setIsInErrorState,
|
setIsInErrorState,
|
||||||
showGenericMessageModal,
|
showGenericMessageModal,
|
||||||
|
showOutOfSyncModal,
|
||||||
t,
|
t,
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
|
@ -104,9 +104,9 @@ export const IdeReactProvider: FC = ({ children }) => {
|
||||||
user_id: getMeta('ol-user_id'),
|
user_id: getMeta('ol-user_id'),
|
||||||
project_id: projectId,
|
project_id: projectId,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
client_id: socket.socket.sessionid,
|
client_id: socket.socket?.sessionid,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
transport: socket.socket.transport,
|
transport: socket.socket?.transport?.name,
|
||||||
client_now: new Date(),
|
client_now: new Date(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,8 @@ import useSocketListener from '@/features/ide-react/hooks/use-socket-listener'
|
||||||
import useEventListener from '@/shared/hooks/use-event-listener'
|
import useEventListener from '@/shared/hooks/use-event-listener'
|
||||||
import { FileTreeFindResult } from '@/features/ide-react/types/file-tree'
|
import { FileTreeFindResult } from '@/features/ide-react/types/file-tree'
|
||||||
import { Project } from '../../../../../types/project'
|
import { Project } from '../../../../../types/project'
|
||||||
|
import { useModalsContext } from '@/features/ide-react/context/modals-context'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
type DocumentMetadata = {
|
type DocumentMetadata = {
|
||||||
labels: string[]
|
labels: string[]
|
||||||
|
@ -45,12 +47,14 @@ const MetadataContext = createContext<MetadataContextValue | undefined>(
|
||||||
)
|
)
|
||||||
|
|
||||||
export const MetadataProvider: FC = ({ children }) => {
|
export const MetadataProvider: FC = ({ children }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const ide = useIdeContext()
|
const ide = useIdeContext()
|
||||||
const { eventEmitter, projectId } = useIdeReactContext()
|
const { eventEmitter, projectId } = useIdeReactContext()
|
||||||
const { socket } = useConnectionContext()
|
const { socket } = useConnectionContext()
|
||||||
const { onlineUsersCount } = useOnlineUsersContext()
|
const { onlineUsersCount } = useOnlineUsersContext()
|
||||||
const { permissionsLevel } = useEditorContext()
|
const { permissionsLevel } = useEditorContext()
|
||||||
const { currentDocument } = useEditorManagerContext()
|
const { currentDocument } = useEditorManagerContext()
|
||||||
|
const { showGenericMessageModal } = useModalsContext()
|
||||||
|
|
||||||
const [documents, setDocuments] = useState<DocumentsMetadata>({})
|
const [documents, setDocuments] = useState<DocumentsMetadata>({})
|
||||||
|
|
||||||
|
@ -180,17 +184,10 @@ export const MetadataProvider: FC = ({ children }) => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleProjectJoined = ({ project }: { project: Project }) => {
|
const handleProjectJoined = ({ project }: { project: Project }) => {
|
||||||
if (project.deletedByExternalDataSource) {
|
if (project.deletedByExternalDataSource) {
|
||||||
// TODO: MIGRATION: Show generic message modal here
|
showGenericMessageModal(
|
||||||
/*
|
t('project_renamed_or_deleted'),
|
||||||
ide.showGenericMessageModal(
|
t('project_renamed_or_deleted_detail')
|
||||||
'Project Renamed or Deleted',
|
|
||||||
`\
|
|
||||||
This project has either been renamed or deleted by an external data source such as Dropbox.
|
|
||||||
We don't want to delete your data on Overleaf, so this project still contains your history and collaborators.
|
|
||||||
If the project has been renamed please look in your project list for a new project under the new name.\
|
|
||||||
`
|
|
||||||
)
|
)
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
if (permissionsLevel !== 'readOnly') {
|
if (permissionsLevel !== 'readOnly') {
|
||||||
|
@ -204,7 +201,13 @@ If the project has been renamed please look in your project list for a new proje
|
||||||
return () => {
|
return () => {
|
||||||
eventEmitter.off('project:joined', handleProjectJoined)
|
eventEmitter.off('project:joined', handleProjectJoined)
|
||||||
}
|
}
|
||||||
}, [eventEmitter, loadProjectMetaFromServer, permissionsLevel])
|
}, [
|
||||||
|
eventEmitter,
|
||||||
|
loadProjectMetaFromServer,
|
||||||
|
permissionsLevel,
|
||||||
|
showGenericMessageModal,
|
||||||
|
t,
|
||||||
|
])
|
||||||
|
|
||||||
const value = useMemo<MetadataContextValue>(
|
const value = useMemo<MetadataContextValue>(
|
||||||
() => ({
|
() => ({
|
||||||
|
|
|
@ -9,6 +9,10 @@ import {
|
||||||
import GenericMessageModal, {
|
import GenericMessageModal, {
|
||||||
GenericMessageModalOwnProps,
|
GenericMessageModalOwnProps,
|
||||||
} from '@/features/ide-react/components/modals/generic-message-modal'
|
} from '@/features/ide-react/components/modals/generic-message-modal'
|
||||||
|
import OutOfSyncModal, {
|
||||||
|
OutOfSyncModalProps,
|
||||||
|
} from '@/features/ide-react/components/modals/out-of-sync-modal'
|
||||||
|
import LockEditorMessageModal from '@/features/ide-react/components/modals/lock-editor-message-modal'
|
||||||
|
|
||||||
type ModalsContextValue = {
|
type ModalsContextValue = {
|
||||||
genericModalVisible: boolean
|
genericModalVisible: boolean
|
||||||
|
@ -16,6 +20,10 @@ type ModalsContextValue = {
|
||||||
title: GenericMessageModalOwnProps['title'],
|
title: GenericMessageModalOwnProps['title'],
|
||||||
message: GenericMessageModalOwnProps['message']
|
message: GenericMessageModalOwnProps['message']
|
||||||
) => void
|
) => void
|
||||||
|
showOutOfSyncModal: (
|
||||||
|
editorContent: OutOfSyncModalProps['editorContent']
|
||||||
|
) => void
|
||||||
|
showLockEditorMessageModal: (delay: number) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const ModalsContext = createContext<ModalsContextValue | undefined>(undefined)
|
const ModalsContext = createContext<ModalsContextValue | undefined>(undefined)
|
||||||
|
@ -25,6 +33,18 @@ export const ModalsContextProvider: FC = ({ children }) => {
|
||||||
const [genericMessageModalData, setGenericMessageModalData] =
|
const [genericMessageModalData, setGenericMessageModalData] =
|
||||||
useState<GenericMessageModalOwnProps>({ title: '', message: '' })
|
useState<GenericMessageModalOwnProps>({ title: '', message: '' })
|
||||||
|
|
||||||
|
const [shouldShowOutOfSyncModal, setShouldShowOutOfSyncModal] =
|
||||||
|
useState(false)
|
||||||
|
const [outOfSyncModalData, setOutOfSyncModalData] = useState({
|
||||||
|
editorContent: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const [shouldShowLockEditorModal, setShouldShowLockEditorModal] =
|
||||||
|
useState(false)
|
||||||
|
const [lockEditorModalData, setLockEditorModalData] = useState({
|
||||||
|
delay: 0,
|
||||||
|
})
|
||||||
|
|
||||||
const handleHideGenericModal = useCallback(() => {
|
const handleHideGenericModal = useCallback(() => {
|
||||||
setShowGenericModal(false)
|
setShowGenericModal(false)
|
||||||
}, [])
|
}, [])
|
||||||
|
@ -40,12 +60,33 @@ export const ModalsContextProvider: FC = ({ children }) => {
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const handleHideOutOfSyncModal = useCallback(() => {
|
||||||
|
setShouldShowOutOfSyncModal(false)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const showOutOfSyncModal = useCallback((editorContent: string) => {
|
||||||
|
setOutOfSyncModalData({ editorContent })
|
||||||
|
setShouldShowOutOfSyncModal(true)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const showLockEditorMessageModal = useCallback((delay: number) => {
|
||||||
|
setLockEditorModalData({ delay })
|
||||||
|
setShouldShowLockEditorModal(true)
|
||||||
|
}, [])
|
||||||
|
|
||||||
const value = useMemo<ModalsContextValue>(
|
const value = useMemo<ModalsContextValue>(
|
||||||
() => ({
|
() => ({
|
||||||
showGenericMessageModal,
|
showGenericMessageModal,
|
||||||
genericModalVisible: showGenericModal,
|
genericModalVisible: showGenericModal,
|
||||||
|
showOutOfSyncModal,
|
||||||
|
showLockEditorMessageModal,
|
||||||
}),
|
}),
|
||||||
[showGenericMessageModal, showGenericModal]
|
[
|
||||||
|
showGenericMessageModal,
|
||||||
|
showGenericModal,
|
||||||
|
showOutOfSyncModal,
|
||||||
|
showLockEditorMessageModal,
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -56,6 +97,15 @@ export const ModalsContextProvider: FC = ({ children }) => {
|
||||||
onHide={handleHideGenericModal}
|
onHide={handleHideGenericModal}
|
||||||
{...genericMessageModalData}
|
{...genericMessageModalData}
|
||||||
/>
|
/>
|
||||||
|
<OutOfSyncModal
|
||||||
|
{...outOfSyncModalData}
|
||||||
|
show={shouldShowOutOfSyncModal}
|
||||||
|
onHide={handleHideOutOfSyncModal}
|
||||||
|
/>
|
||||||
|
<LockEditorMessageModal
|
||||||
|
{...lockEditorModalData}
|
||||||
|
show={shouldShowLockEditorModal}
|
||||||
|
/>
|
||||||
</ModalsContext.Provider>
|
</ModalsContext.Provider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -740,6 +740,7 @@
|
||||||
"helps_us_tailor_your_experience": "This helps us tailor your Overleaf experience.",
|
"helps_us_tailor_your_experience": "This helps us tailor your Overleaf experience.",
|
||||||
"hide_configuration": "Hide configuration",
|
"hide_configuration": "Hide configuration",
|
||||||
"hide_document_preamble": "Hide document preamble",
|
"hide_document_preamble": "Hide document preamble",
|
||||||
|
"hide_local_file_contents": "Hide Local File Contents",
|
||||||
"hide_outline": "Hide File outline",
|
"hide_outline": "Hide File outline",
|
||||||
"history": "History",
|
"history": "History",
|
||||||
"history_add_label": "Add label",
|
"history_add_label": "Add label",
|
||||||
|
@ -1227,6 +1228,8 @@
|
||||||
"other_output_files": "Download other output files",
|
"other_output_files": "Download other output files",
|
||||||
"other_sessions": "Other Sessions",
|
"other_sessions": "Other Sessions",
|
||||||
"our_values": "Our values",
|
"our_values": "Our values",
|
||||||
|
"out_of_sync": "Out of sync",
|
||||||
|
"out_of_sync_detail": "Sorry, this file has gone out of sync and we need to do a full refresh.<0 /><1>Please see this help guide for more information</1>",
|
||||||
"output_file": "Output file",
|
"output_file": "Output file",
|
||||||
"over": "over",
|
"over": "over",
|
||||||
"over_x_templates_easy_getting_started": "There are thousands of __templates__ in our template gallery, so it’s really easy to get started, whether you’re writing a journal article, thesis, CV or something else.",
|
"over_x_templates_easy_getting_started": "There are thousands of __templates__ in our template gallery, so it’s really easy to get started, whether you’re writing a journal article, thesis, CV or something else.",
|
||||||
|
@ -1309,6 +1312,7 @@
|
||||||
"please_select_an_output_file": "Please Select an Output File",
|
"please_select_an_output_file": "Please Select an Output File",
|
||||||
"please_set_a_password": "Please set a password",
|
"please_set_a_password": "Please set a password",
|
||||||
"please_set_main_file": "Please choose the main file for this project in the project menu. ",
|
"please_set_main_file": "Please choose the main file for this project in the project menu. ",
|
||||||
|
"please_wait": "Please wait",
|
||||||
"plus_additional_collaborators_document_history_track_changes_and_more": "(plus additional collaborators, document history, track changes, and more).",
|
"plus_additional_collaborators_document_history_track_changes_and_more": "(plus additional collaborators, document history, track changes, and more).",
|
||||||
"plus_more": "plus more",
|
"plus_more": "plus more",
|
||||||
"plus_upgraded_accounts_receive": "Plus with an upgraded account you get",
|
"plus_upgraded_accounts_receive": "Plus with an upgraded account you get",
|
||||||
|
@ -1360,6 +1364,8 @@
|
||||||
"project_owner_plus_10": "Project author + 10",
|
"project_owner_plus_10": "Project author + 10",
|
||||||
"project_ownership_transfer_confirmation_1": "Are you sure you want to make <0>__user__</0> the owner of <1>__project__</1>?",
|
"project_ownership_transfer_confirmation_1": "Are you sure you want to make <0>__user__</0> the owner of <1>__project__</1>?",
|
||||||
"project_ownership_transfer_confirmation_2": "This action cannot be undone. The new owner will be notified and will be able to change project access settings (including removing your own access).",
|
"project_ownership_transfer_confirmation_2": "This action cannot be undone. The new owner will be notified and will be able to change project access settings (including removing your own access).",
|
||||||
|
"project_renamed_or_deleted": "Project Renamed or Deleted",
|
||||||
|
"project_renamed_or_deleted_detail": "This project has either been renamed or deleted by an external data source such as Dropbox. We don’t want to delete your data on Overleaf, so this project still contains your history and collaborators. If the project has been renamed please look in your project list for a new project under the new name.",
|
||||||
"project_synced_with_git_repo_at": "This project is synced with the GitHub repository at",
|
"project_synced_with_git_repo_at": "This project is synced with the GitHub repository at",
|
||||||
"project_synchronisation": "Project Synchronisation",
|
"project_synchronisation": "Project Synchronisation",
|
||||||
"project_timed_out_enable_stop_on_first_error": "<0>Enable “Stop on first error”</0> to help you find and fix errors right away.",
|
"project_timed_out_enable_stop_on_first_error": "<0>Enable “Stop on first error”</0> to help you find and fix errors right away.",
|
||||||
|
@ -1625,6 +1631,7 @@
|
||||||
"show_in_code": "Show in code",
|
"show_in_code": "Show in code",
|
||||||
"show_in_pdf": "Show in PDF",
|
"show_in_pdf": "Show in PDF",
|
||||||
"show_less": "show less",
|
"show_less": "show less",
|
||||||
|
"show_local_file_contents": "Show Local File Contents",
|
||||||
"show_outline": "Show File outline",
|
"show_outline": "Show File outline",
|
||||||
"show_x_more": "Show __x__ more",
|
"show_x_more": "Show __x__ more",
|
||||||
"show_x_more_projects": "Show __x__ more projects",
|
"show_x_more_projects": "Show __x__ more projects",
|
||||||
|
@ -2027,6 +2034,7 @@
|
||||||
"welcome_to_sl": "Welcome to __appName__!",
|
"welcome_to_sl": "Welcome to __appName__!",
|
||||||
"were_in_the_process_of_reducing_compile_timeout_which_may_affect_this_project": "We’re in the process of <0>reducing the compile timeout limit</0> on our free plan, which may affect this project in future.",
|
"were_in_the_process_of_reducing_compile_timeout_which_may_affect_this_project": "We’re in the process of <0>reducing the compile timeout limit</0> on our free plan, which may affect this project in future.",
|
||||||
"were_in_the_process_of_reducing_compile_timeout_which_may_affect_your_project": "We’re in the process of <0>reducing the compile timeout limit</0> on our free plan, which may affect your project in future.",
|
"were_in_the_process_of_reducing_compile_timeout_which_may_affect_your_project": "We’re in the process of <0>reducing the compile timeout limit</0> on our free plan, which may affect your project in future.",
|
||||||
|
"were_performing_maintenance": "We’re performing maintenance on Overleaf and you need to wait a moment. Sorry for any inconvenience. The editor will refresh automatically in __seconds__ seconds.",
|
||||||
"weve_recently_reduced_the_compile_timeout_limit_which_may_have_affected_this_project": "We’ve recently <0>reduced the compile timeout limit</0> on our free plan, which may have affected this project.",
|
"weve_recently_reduced_the_compile_timeout_limit_which_may_have_affected_this_project": "We’ve recently <0>reduced the compile timeout limit</0> on our free plan, which may have affected this project.",
|
||||||
"weve_recently_reduced_the_compile_timeout_limit_which_may_have_affected_your_project": "We’ve recently <0>reduced the compile timeout limit</0> on our free plan, which may have affected your project.",
|
"weve_recently_reduced_the_compile_timeout_limit_which_may_have_affected_your_project": "We’ve recently <0>reduced the compile timeout limit</0> on our free plan, which may have affected your project.",
|
||||||
"what_does_this_mean": "What does this mean?",
|
"what_does_this_mean": "What does this mean?",
|
||||||
|
|
Loading…
Reference in a new issue