mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #13133 from overleaf/mj-wait-upload
[cm6] Wait for file to be ready before inserting code from figure modal GitOrigin-RevId: 5c4e8f243518bacc7b6ef4272eaf44995192efbc
This commit is contained in:
parent
5bbe427ed0
commit
480672bf7a
6 changed files with 128 additions and 3 deletions
|
@ -20,6 +20,8 @@ import { postJSON } from '../../../../../infrastructure/fetch-json'
|
|||
import { useProjectContext } from '../../../../../shared/context/project-context'
|
||||
import { FileRelocator } from '../file-relocator'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { waitForFileTreeUpdate } from '../../../extensions/figure-modal'
|
||||
import { useCodeMirrorViewContext } from '../../codemirror-editor'
|
||||
|
||||
function suggestName(path: string) {
|
||||
const parts = path.split('/')
|
||||
|
@ -28,6 +30,7 @@ function suggestName(path: string) {
|
|||
|
||||
export const FigureModalOtherProjectSource: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const view = useCodeMirrorViewContext()
|
||||
const { dispatch } = useFigureModalContext()
|
||||
const { _id: projectId } = useProjectContext()
|
||||
const { loading: projectsLoading, data: projects, error } = useUserProjects()
|
||||
|
@ -108,9 +111,11 @@ export const FigureModalOtherProjectSource: FC = () => {
|
|||
|
||||
dispatch({
|
||||
getPath: async () => {
|
||||
const fileTreeUpdate = waitForFileTreeUpdate(view)
|
||||
await postJSON(`/project/${projectId}/linked_file`, {
|
||||
body,
|
||||
})
|
||||
await fileTreeUpdate.withTimeout(500)
|
||||
return targetFolder.path === '' && targetFolder.name === 'rootFolder'
|
||||
? `${newName}`
|
||||
: `${targetFolder.path ? targetFolder.path + '/' : ''}${
|
||||
|
|
|
@ -14,6 +14,8 @@ import classNames from 'classnames'
|
|||
import { Button } from 'react-bootstrap'
|
||||
import { FileRelocator } from '../file-relocator'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useCodeMirrorViewContext } from '../../codemirror-editor'
|
||||
import { waitForFileTreeUpdate } from '../../../extensions/figure-modal'
|
||||
|
||||
const maxFileSize = window.ExposedSettings.maxUploadSize
|
||||
|
||||
|
@ -28,6 +30,7 @@ export enum FileUploadStatus {
|
|||
|
||||
export const FigureModalUploadFileSource: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const view = useCodeMirrorViewContext()
|
||||
const { dispatch } = useFigureModalContext()
|
||||
const { _id: projectId } = useProjectContext()
|
||||
const [, rootFolder] = useCurrentProjectFolders()
|
||||
|
@ -68,7 +71,9 @@ export const FigureModalUploadFileSource: FC = () => {
|
|||
}
|
||||
dispatch({
|
||||
getPath: async () => {
|
||||
const fileTreeUpdate = waitForFileTreeUpdate(view)
|
||||
const uploadResult = await uppy.upload()
|
||||
await fileTreeUpdate.withTimeout(500)
|
||||
if (!uploadResult.successful) {
|
||||
throw new Error('Upload failed')
|
||||
}
|
||||
|
@ -81,7 +86,7 @@ export const FigureModalUploadFileSource: FC = () => {
|
|||
},
|
||||
})
|
||||
},
|
||||
[dispatch, rootFolder, uppy]
|
||||
[dispatch, rootFolder, uppy, view]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -6,14 +6,19 @@ import { File } from '../../../utils/file'
|
|||
import { useCurrentProjectFolders } from '../../../hooks/useCurrentProjectFolders'
|
||||
import { FileRelocator } from '../file-relocator'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useCodeMirrorViewContext } from '../../codemirror-editor'
|
||||
import { EditorView } from '@codemirror/view'
|
||||
import { waitForFileTreeUpdate } from '../../../extensions/figure-modal'
|
||||
|
||||
function generateLinkedFileFetcher(
|
||||
projectId: string,
|
||||
url: string,
|
||||
name: string,
|
||||
folder: File
|
||||
folder: File,
|
||||
view: EditorView
|
||||
) {
|
||||
return async () => {
|
||||
const fileTreeUpdate = waitForFileTreeUpdate(view)
|
||||
await postJSON(`/project/${projectId}/linked_file`, {
|
||||
body: {
|
||||
parent_folder_id: folder.id,
|
||||
|
@ -24,6 +29,8 @@ function generateLinkedFileFetcher(
|
|||
},
|
||||
},
|
||||
})
|
||||
await fileTreeUpdate.withTimeout(500)
|
||||
|
||||
return folder.path === '' && folder.name === 'rootFolder'
|
||||
? `${name}`
|
||||
: `${folder.path ? folder.path + '/' : ''}${folder.name}/${name}`
|
||||
|
@ -31,6 +38,7 @@ function generateLinkedFileFetcher(
|
|||
}
|
||||
|
||||
export const FigureModalUrlSource: FC = () => {
|
||||
const view = useCodeMirrorViewContext()
|
||||
const { t } = useTranslation()
|
||||
const [url, setUrl] = useState<string>('')
|
||||
const [nameDirty, setNameDirty] = useState<boolean>(false)
|
||||
|
@ -53,7 +61,8 @@ export const FigureModalUrlSource: FC = () => {
|
|||
projectId,
|
||||
newUrl,
|
||||
newName,
|
||||
folder ?? rootFile
|
||||
folder ?? rootFile,
|
||||
view
|
||||
),
|
||||
})
|
||||
} else if (getPath) {
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
import { StateEffect, StateEffectType, StateField } from '@codemirror/state'
|
||||
import { EditorView } from '@codemirror/view'
|
||||
|
||||
type EffectListenerOptions = {
|
||||
once: boolean
|
||||
}
|
||||
|
||||
type EffectListener = {
|
||||
effect: StateEffectType<any>
|
||||
callback: (value: any) => any
|
||||
options?: EffectListenerOptions
|
||||
}
|
||||
|
||||
const addEffectListenerEffect = StateEffect.define<EffectListener>()
|
||||
const removeEffectListenerEffect = StateEffect.define<EffectListener>()
|
||||
|
||||
export const effectListeners = () => [effectListenersField]
|
||||
|
||||
const effectListenersField = StateField.define<EffectListener[]>({
|
||||
create: () => [],
|
||||
update(value, transaction) {
|
||||
for (const effect of transaction.effects) {
|
||||
if (effect.is(addEffectListenerEffect)) {
|
||||
value.push(effect.value)
|
||||
}
|
||||
if (effect.is(removeEffectListenerEffect)) {
|
||||
value = value.filter(
|
||||
listener =>
|
||||
!(
|
||||
listener.effect === effect.value.effect &&
|
||||
listener.callback === effect.value.callback
|
||||
)
|
||||
)
|
||||
}
|
||||
for (let i = 0; i < value.length; ++i) {
|
||||
const listener = value[i]
|
||||
if (effect.is(listener.effect)) {
|
||||
// Invoke the callback after the transaction
|
||||
setTimeout(() => listener.callback(effect.value))
|
||||
if (listener.options?.once) {
|
||||
// Remove the effectListener
|
||||
value.splice(i, 1)
|
||||
// Keep index the same for the next iteration, since we've removed
|
||||
// an element
|
||||
--i
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return value
|
||||
},
|
||||
})
|
||||
|
||||
export const addEffectListener = <T>(
|
||||
view: EditorView,
|
||||
effect: StateEffectType<T>,
|
||||
callback: (value: T) => any,
|
||||
options?: EffectListenerOptions
|
||||
) => {
|
||||
view.dispatch({
|
||||
effects: addEffectListenerEffect.of({ effect, callback, options }),
|
||||
})
|
||||
}
|
||||
|
||||
export const removeEffectListener = <T>(
|
||||
view: EditorView,
|
||||
effect: StateEffectType<T>,
|
||||
callback: (value: T) => any
|
||||
) => {
|
||||
view.dispatch({
|
||||
effects: removeEffectListenerEffect.of({ effect, callback }),
|
||||
})
|
||||
}
|
|
@ -4,6 +4,9 @@ import {
|
|||
StateEffect,
|
||||
StateField,
|
||||
} from '@codemirror/state'
|
||||
import { EditorView } from '@codemirror/view'
|
||||
import { addEffectListener, removeEffectListener } from './effect-listeners'
|
||||
import { setMetadataEffect } from './language'
|
||||
|
||||
type NestedReadonly<T> = {
|
||||
readonly [P in keyof T]: NestedReadonly<T[P]>
|
||||
|
@ -127,3 +130,31 @@ export const editFigureData = StateField.define<FigureData | null>({
|
|||
})
|
||||
|
||||
export const figureModal = (): Extension => [editFigureData]
|
||||
|
||||
export function waitForFileTreeUpdate(view: EditorView) {
|
||||
const abortController = new AbortController()
|
||||
const promise = new Promise<void>(resolve => {
|
||||
const abort = () => {
|
||||
console.warn('Aborting wait for file tree update')
|
||||
removeEffectListener(view, setMetadataEffect, listener)
|
||||
resolve()
|
||||
}
|
||||
function listener() {
|
||||
if (abortController.signal.aborted) {
|
||||
// We've already handled this
|
||||
return
|
||||
}
|
||||
abortController.signal.removeEventListener('abort', abort)
|
||||
resolve()
|
||||
}
|
||||
abortController.signal.addEventListener('abort', abort, { once: true })
|
||||
addEffectListener(view, setMetadataEffect, listener, { once: true })
|
||||
})
|
||||
return {
|
||||
withTimeout(afterMs = 500) {
|
||||
setTimeout(() => abortController.abort(), afterMs)
|
||||
return promise
|
||||
},
|
||||
promise,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ import { indentationMarkers } from './indentation-markers'
|
|||
import { codemirrorDevTools } from '../languages/latex/codemirror-dev-tools'
|
||||
import { keymaps } from './keymaps'
|
||||
import { shortcuts } from './shortcuts'
|
||||
import { effectListeners } from './effect-listeners'
|
||||
|
||||
const moduleExtensions: Array<() => Extension> = importOverleafModules(
|
||||
'sourceEditorExtensions'
|
||||
|
@ -117,4 +118,5 @@ export const createExtensions = (options: Record<string, any>): Extension[] => [
|
|||
exceptionLogger(),
|
||||
moduleExtensions.map(extension => extension()),
|
||||
thirdPartyExtensions(),
|
||||
effectListeners(),
|
||||
]
|
||||
|
|
Loading…
Reference in a new issue