mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #15342 from overleaf/td-remove-file-tree-manager-in-react
Remove use of FileTreeManager in React code GitOrigin-RevId: f15bc9b4f84e0f65709b9850ed8cc5d3637efa7f
This commit is contained in:
parent
01439641ca
commit
13f246a85e
22 changed files with 643 additions and 205 deletions
|
@ -0,0 +1,81 @@
|
|||
import { createContext, FC, useCallback, useContext, useMemo } from 'react'
|
||||
import { Folder } from '../../../../../types/folder'
|
||||
import { useFileTreeData } from '@/shared/context/file-tree-data-context'
|
||||
import getMeta from '@/utils/meta'
|
||||
import {
|
||||
findEntityByPath,
|
||||
previewByPath,
|
||||
dirname,
|
||||
FindResult,
|
||||
pathInFolder,
|
||||
} from '@/features/file-tree/util/path'
|
||||
import { PreviewPath } from '../../../../../types/preview-path'
|
||||
|
||||
type FileTreePathContextValue = {
|
||||
pathInFolder: (id: string) => string | null
|
||||
findEntityByPath: (path: string) => FindResult | null
|
||||
previewByPath: (path: string) => PreviewPath | null
|
||||
dirname: (id: string) => string | null
|
||||
}
|
||||
|
||||
export const FileTreePathContext = createContext<
|
||||
FileTreePathContextValue | undefined
|
||||
>(undefined)
|
||||
|
||||
export const FileTreePathProvider: FC = ({ children }) => {
|
||||
const { fileTreeData }: { fileTreeData: Folder } = useFileTreeData()
|
||||
const projectId = getMeta('ol-project_id') as string
|
||||
|
||||
const pathInFileTree = useCallback(
|
||||
(id: string) => pathInFolder(fileTreeData, id),
|
||||
[fileTreeData]
|
||||
)
|
||||
|
||||
const findEntityByPathInFileTree = useCallback(
|
||||
(path: string) => findEntityByPath(fileTreeData, path),
|
||||
[fileTreeData]
|
||||
)
|
||||
|
||||
const previewByPathInFileTree = useCallback(
|
||||
(path: string) => previewByPath(fileTreeData, projectId, path),
|
||||
[fileTreeData, projectId]
|
||||
)
|
||||
|
||||
const dirnameInFileTree = useCallback(
|
||||
(id: string) => dirname(fileTreeData, id),
|
||||
[fileTreeData]
|
||||
)
|
||||
|
||||
const value = useMemo<FileTreePathContextValue>(
|
||||
() => ({
|
||||
pathInFolder: pathInFileTree,
|
||||
findEntityByPath: findEntityByPathInFileTree,
|
||||
previewByPath: previewByPathInFileTree,
|
||||
dirname: dirnameInFileTree,
|
||||
}),
|
||||
[
|
||||
pathInFileTree,
|
||||
findEntityByPathInFileTree,
|
||||
previewByPathInFileTree,
|
||||
dirnameInFileTree,
|
||||
]
|
||||
)
|
||||
|
||||
return (
|
||||
<FileTreePathContext.Provider value={value}>
|
||||
{children}
|
||||
</FileTreePathContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useFileTreePathContext(): FileTreePathContextValue {
|
||||
const context = useContext(FileTreePathContext)
|
||||
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
'useFileTreePathContext is only available inside FileTreePathProvider'
|
||||
)
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
|
@ -46,7 +46,7 @@ export function createEntityInTree(tree, parentFolderId, newEntityData) {
|
|||
}
|
||||
|
||||
function mutateInTree(tree, id, mutationFunction) {
|
||||
if (tree._id === id) {
|
||||
if (!id || tree._id === id) {
|
||||
// covers the root folder case: it has no parent so in order to use
|
||||
// mutationFunction we pass an empty array as the parent and return the
|
||||
// mutated tree directly
|
||||
|
|
138
services/web/frontend/js/features/file-tree/util/path.ts
Normal file
138
services/web/frontend/js/features/file-tree/util/path.ts
Normal file
|
@ -0,0 +1,138 @@
|
|||
import { Folder } from '../../../../../types/folder'
|
||||
import { FileTreeEntity } from '../../../../../types/file-tree-entity'
|
||||
import { Doc } from '../../../../../types/doc'
|
||||
import { FileRef } from '../../../../../types/file-ref'
|
||||
import { PreviewPath } from '../../../../../types/preview-path'
|
||||
|
||||
type DocFindResult = {
|
||||
entity: Doc
|
||||
type: 'doc'
|
||||
}
|
||||
|
||||
type FolderFindResult = {
|
||||
entity: Folder
|
||||
type: 'folder'
|
||||
}
|
||||
|
||||
type FileRefFindResult = {
|
||||
entity: FileRef
|
||||
type: 'fileRef'
|
||||
}
|
||||
|
||||
export type FindResult = DocFindResult | FolderFindResult | FileRefFindResult
|
||||
|
||||
// Finds the entity with a given ID in the tree represented by `folder` and
|
||||
// returns a path to that entity, represented by an array of folders starting at
|
||||
// the root plus the entity itself
|
||||
function pathComponentsInFolder(
|
||||
folder: Folder,
|
||||
id: string,
|
||||
ancestors: FileTreeEntity[] = []
|
||||
): FileTreeEntity[] | null {
|
||||
const docOrFileRef =
|
||||
folder.docs.find(doc => doc._id === id) ||
|
||||
folder.fileRefs.find(fileRef => fileRef._id === id)
|
||||
if (docOrFileRef) {
|
||||
return ancestors.concat([docOrFileRef])
|
||||
}
|
||||
|
||||
for (const subfolder of folder.folders) {
|
||||
if (subfolder._id === id) {
|
||||
return ancestors.concat([subfolder])
|
||||
} else {
|
||||
const path = pathComponentsInFolder(
|
||||
subfolder,
|
||||
id,
|
||||
ancestors.concat([subfolder])
|
||||
)
|
||||
if (path !== null) {
|
||||
return path
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
// Finds the entity with a given ID in the tree represented by `folder` and
|
||||
// returns a path to that entity as a string
|
||||
export function pathInFolder(folder: Folder, id: string): string | null {
|
||||
return (
|
||||
pathComponentsInFolder(folder, id)
|
||||
?.map(entity => entity.name)
|
||||
.join('/') || null
|
||||
)
|
||||
}
|
||||
|
||||
export function findEntityByPath(
|
||||
folder: Folder,
|
||||
path: string
|
||||
): FindResult | null {
|
||||
if (path === '') {
|
||||
return { entity: folder, type: 'folder' }
|
||||
}
|
||||
|
||||
const parts = path.split('/')
|
||||
const name = parts.shift()
|
||||
const rest = parts.join('/')
|
||||
|
||||
if (name === '.') {
|
||||
return findEntityByPath(folder, rest)
|
||||
}
|
||||
|
||||
const doc = folder.docs.find(doc => doc.name === name)
|
||||
if (doc) {
|
||||
return { entity: doc, type: 'doc' }
|
||||
}
|
||||
|
||||
const fileRef = folder.fileRefs.find(fileRef => fileRef.name === name)
|
||||
if (fileRef) {
|
||||
return { entity: fileRef, type: 'fileRef' }
|
||||
}
|
||||
|
||||
for (const subfolder of folder.folders) {
|
||||
if (subfolder.name === name) {
|
||||
if (rest === '') {
|
||||
return { entity: subfolder, type: 'folder' }
|
||||
} else {
|
||||
return findEntityByPath(subfolder, rest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export function previewByPath(
|
||||
folder: Folder,
|
||||
projectId: string,
|
||||
path: string
|
||||
): PreviewPath | null {
|
||||
for (const suffix of [
|
||||
'',
|
||||
'.png',
|
||||
'.jpg',
|
||||
'.jpeg',
|
||||
'.pdf',
|
||||
'.PNG',
|
||||
'.JPG',
|
||||
'.JPEG',
|
||||
'.PDF',
|
||||
]) {
|
||||
const result = findEntityByPath(folder, path + suffix)
|
||||
|
||||
if (result) {
|
||||
const { name, _id: id } = result.entity
|
||||
return {
|
||||
url: `/project/${projectId}/file/${id}`,
|
||||
extension: name.slice(name.lastIndexOf('.')),
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export function dirname(fileTreeData: Folder, id: string) {
|
||||
const path = pathInFolder(fileTreeData, id)
|
||||
return path?.split('/').slice(0, -1).join('/') || null
|
||||
}
|
|
@ -1,51 +1,100 @@
|
|||
import { sendMB } from '../../../../infrastructure/event-tracking'
|
||||
import { useIdeContext } from '../../../../shared/context/ide-context'
|
||||
import { useLayoutContext } from '../../../../shared/context/layout-context'
|
||||
import useAsync from '../../../../shared/hooks/use-async'
|
||||
import { restoreFile } from '../../services/api'
|
||||
import { isFileRemoved } from '../../utils/file-diff'
|
||||
import { waitFor } from '../../utils/wait-for'
|
||||
import { useHistoryContext } from '../history-context'
|
||||
import type { HistoryContextValue } from '../types/history-context-value'
|
||||
import { useErrorHandler } from 'react-error-boundary'
|
||||
import { useFileTreeData } from '@/shared/context/file-tree-data-context'
|
||||
import { findInTree } from '@/features/file-tree/util/find-in-tree'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { RestoreFileResponse } from '@/features/history/services/types/restore-file'
|
||||
|
||||
type RestorationState =
|
||||
| 'idle'
|
||||
| 'restoring'
|
||||
| 'waitingForFileTree'
|
||||
| 'complete'
|
||||
| 'error'
|
||||
| 'timedOut'
|
||||
|
||||
export function useRestoreDeletedFile() {
|
||||
const { isLoading, runAsync } = useAsync()
|
||||
const { projectId } = useHistoryContext()
|
||||
const ide = useIdeContext()
|
||||
const { setView } = useLayoutContext()
|
||||
const handleError = useErrorHandler()
|
||||
const { fileTreeData } = useFileTreeData()
|
||||
const [state, setState] = useState<RestorationState>('idle')
|
||||
const [restoredFileMetadata, setRestoredFileMetadata] =
|
||||
useState<RestoreFileResponse | null>(null)
|
||||
|
||||
const restoreDeletedFile = async (
|
||||
selection: HistoryContextValue['selection']
|
||||
) => {
|
||||
const isLoading = state === 'restoring' || state === 'waitingForFileTree'
|
||||
|
||||
useEffect(() => {
|
||||
if (state === 'waitingForFileTree' && restoredFileMetadata) {
|
||||
const result = findInTree(fileTreeData, restoredFileMetadata.id)
|
||||
if (result) {
|
||||
setState('complete')
|
||||
const { _id: id } = result.entity
|
||||
setView('editor')
|
||||
if (restoredFileMetadata.type === 'doc') {
|
||||
ide.editorManager.openDocId(id)
|
||||
} else {
|
||||
ide.binaryFilesManager.openFileWithId(id)
|
||||
}
|
||||
// Get the file tree to select the entity that has just been restored
|
||||
window.dispatchEvent(new CustomEvent('editor.openDoc', { detail: id }))
|
||||
}
|
||||
}
|
||||
}, [
|
||||
state,
|
||||
fileTreeData,
|
||||
restoredFileMetadata,
|
||||
ide.editorManager,
|
||||
ide.binaryFilesManager,
|
||||
setView,
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
if (state === 'waitingForFileTree') {
|
||||
const timer = window.setTimeout(() => {
|
||||
setState('timedOut')
|
||||
handleError(new Error('timed out'))
|
||||
}, 3000)
|
||||
|
||||
return () => {
|
||||
window.clearTimeout(timer)
|
||||
}
|
||||
}
|
||||
}, [handleError, state])
|
||||
|
||||
const restoreDeletedFile = useCallback(
|
||||
(selection: HistoryContextValue['selection']) => {
|
||||
const { selectedFile } = selection
|
||||
|
||||
if (selectedFile && selectedFile.pathname && isFileRemoved(selectedFile)) {
|
||||
if (
|
||||
selectedFile &&
|
||||
selectedFile.pathname &&
|
||||
isFileRemoved(selectedFile)
|
||||
) {
|
||||
sendMB('history-v2-restore-deleted')
|
||||
|
||||
await runAsync(
|
||||
restoreFile(projectId, selectedFile)
|
||||
.then(async data => {
|
||||
const { id, type } = data
|
||||
|
||||
const entity = await waitFor(
|
||||
() => ide.fileTreeManager.findEntityById(id),
|
||||
3000
|
||||
)
|
||||
|
||||
if (type === 'doc') {
|
||||
ide.editorManager.openDoc(entity)
|
||||
} else {
|
||||
ide.binaryFilesManager.openFile(entity)
|
||||
setState('restoring')
|
||||
restoreFile(projectId, selectedFile).then(
|
||||
(data: RestoreFileResponse) => {
|
||||
setRestoredFileMetadata(data)
|
||||
setState('waitingForFileTree')
|
||||
},
|
||||
error => {
|
||||
setState('error')
|
||||
handleError(error)
|
||||
}
|
||||
|
||||
setView('editor')
|
||||
})
|
||||
.catch(handleError)
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
[handleError, projectId]
|
||||
)
|
||||
|
||||
return { restoreDeletedFile, isLoading }
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import { useFileTreeData } from '../../../shared/context/file-tree-data-context'
|
|||
import useScopeEventListener from '../../../shared/hooks/use-scope-event-listener'
|
||||
import * as eventTracking from '../../../infrastructure/event-tracking'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
import { useFileTreePathContext } from '@/features/file-tree/contexts/file-tree-path'
|
||||
|
||||
function GoToCodeButton({
|
||||
position,
|
||||
|
@ -118,7 +119,7 @@ function GoToPdfButton({
|
|||
function PdfSynctexControls() {
|
||||
const ide = useIdeContext()
|
||||
|
||||
const { _id: projectId } = useProjectContext()
|
||||
const { _id: projectId, rootDocId } = useProjectContext()
|
||||
|
||||
const { detachRole } = useLayoutContext()
|
||||
|
||||
|
@ -132,6 +133,7 @@ function PdfSynctexControls() {
|
|||
} = useCompileContext()
|
||||
|
||||
const { selectedEntities } = useFileTreeData()
|
||||
const { findEntityByPath, dirname, pathInFolder } = useFileTreePathContext()
|
||||
|
||||
const [cursorPosition, setCursorPosition] = useState(() => {
|
||||
const position = localStorage.getItem(
|
||||
|
@ -162,26 +164,28 @@ function PdfSynctexControls() {
|
|||
|
||||
const getCurrentFilePath = useCallback(() => {
|
||||
const docId = ide.editorManager.getCurrentDocId()
|
||||
const doc = ide.fileTreeManager.findEntityById(docId)
|
||||
|
||||
let path = ide.fileTreeManager.getEntityPath(doc)
|
||||
let path = pathInFolder(docId)
|
||||
|
||||
// If the root file is folder/main.tex, then synctex sees the path as folder/./main.tex
|
||||
const rootDocDirname = ide.fileTreeManager.getRootDocDirname()
|
||||
const rootDocDirname = dirname(rootDocId)
|
||||
|
||||
if (rootDocDirname) {
|
||||
path = path.replace(RegExp(`^${rootDocDirname}`), `${rootDocDirname}/.`)
|
||||
}
|
||||
|
||||
return path
|
||||
}, [ide])
|
||||
}, [dirname, ide.editorManager, pathInFolder, rootDocId])
|
||||
|
||||
const goToCodeLine = useCallback(
|
||||
(file, line) => {
|
||||
if (file) {
|
||||
const doc = ide.fileTreeManager.findEntityByPath(file)
|
||||
const doc = findEntityByPath(file)?.entity
|
||||
if (!doc) {
|
||||
debugConsole.warn(`Document with path ${file} not found`)
|
||||
return
|
||||
}
|
||||
|
||||
ide.editorManager.openDoc(doc, {
|
||||
ide.editorManager.openDocId(doc._id, {
|
||||
gotoLine: line,
|
||||
})
|
||||
} else {
|
||||
|
@ -194,7 +198,7 @@ function PdfSynctexControls() {
|
|||
}, 4000)
|
||||
}
|
||||
},
|
||||
[ide, isMounted, setSynctexError]
|
||||
[findEntityByPath, ide.editorManager, isMounted, setSynctexError]
|
||||
)
|
||||
|
||||
const goToPdfLocation = useCallback(
|
||||
|
|
|
@ -4,6 +4,7 @@ import BibLogParser from '../../../ide/log-parser/bib-log-parser'
|
|||
import { v4 as uuid } from 'uuid'
|
||||
import { enablePdfCaching } from './pdf-caching-flags'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
import { dirname, findEntityByPath } from '@/features/file-tree/util/path'
|
||||
|
||||
// Warnings that may disappear after a second LaTeX pass
|
||||
const TRANSIENT_WARNING_REGEX = /^(Reference|Citation).+undefined on input line/
|
||||
|
@ -133,8 +134,8 @@ export const handleLogFiles = async (outputFiles, data, signal) => {
|
|||
return result
|
||||
}
|
||||
|
||||
export function buildLogEntryAnnotations(entries, fileTreeManager) {
|
||||
const rootDocDirname = fileTreeManager.getRootDocDirname()
|
||||
export function buildLogEntryAnnotations(entries, fileTreeData, rootDocId) {
|
||||
const rootDocDirname = dirname(fileTreeData, rootDocId)
|
||||
|
||||
const logEntryAnnotations = {}
|
||||
|
||||
|
@ -142,14 +143,14 @@ export function buildLogEntryAnnotations(entries, fileTreeManager) {
|
|||
if (entry.file) {
|
||||
entry.file = normalizeFilePath(entry.file, rootDocDirname)
|
||||
|
||||
const entity = fileTreeManager.findEntityByPath(entry.file)
|
||||
const entity = findEntityByPath(fileTreeData, entry.file)?.entity
|
||||
|
||||
if (entity) {
|
||||
if (!(entity.id in logEntryAnnotations)) {
|
||||
logEntryAnnotations[entity.id] = []
|
||||
if (!(entity._id in logEntryAnnotations)) {
|
||||
logEntryAnnotations[entity._id] = []
|
||||
}
|
||||
|
||||
logEntryAnnotations[entity.id].push({
|
||||
logEntryAnnotations[entity._id].push({
|
||||
row: entry.line - 1,
|
||||
type: entry.level === 'error' ? 'error' : 'warning',
|
||||
text: entry.message,
|
||||
|
|
|
@ -66,13 +66,10 @@ import { skipPreambleWithCursor } from './skip-preamble-cursor'
|
|||
import { TableRenderingErrorWidget } from './visual-widgets/table-rendering-error'
|
||||
import { GraphicsWidget } from './visual-widgets/graphics'
|
||||
import { InlineGraphicsWidget } from './visual-widgets/inline-graphics'
|
||||
import { PreviewPath } from '../../../../../../types/preview-path'
|
||||
|
||||
type Options = {
|
||||
fileTreeManager: {
|
||||
getPreviewByPath: (
|
||||
path: string
|
||||
) => { url: string; extension: string } | null
|
||||
}
|
||||
previewByPath: (path: string) => PreviewPath | null
|
||||
}
|
||||
|
||||
function shouldDecorate(
|
||||
|
@ -135,9 +132,7 @@ const hasClosingBrace = (node: SyntaxNode) =>
|
|||
* Decorations that span multiple lines must be contained in a StateField, not a ViewPlugin.
|
||||
*/
|
||||
export const atomicDecorations = (options: Options) => {
|
||||
const getPreviewByPath = (path: string) =>
|
||||
options.fileTreeManager.getPreviewByPath(path)
|
||||
|
||||
const { previewByPath } = options
|
||||
const createDecorations = (
|
||||
state: EditorState,
|
||||
tree: Tree
|
||||
|
@ -895,7 +890,7 @@ export const atomicDecorations = (options: Options) => {
|
|||
Decoration.replace({
|
||||
widget: new Widget(
|
||||
filePath,
|
||||
getPreviewByPath,
|
||||
previewByPath,
|
||||
centered,
|
||||
figureData
|
||||
),
|
||||
|
@ -910,7 +905,7 @@ export const atomicDecorations = (options: Options) => {
|
|||
Decoration.replace({
|
||||
widget: new Widget(
|
||||
filePath,
|
||||
getPreviewByPath,
|
||||
previewByPath,
|
||||
centered,
|
||||
figureData
|
||||
),
|
||||
|
|
|
@ -3,6 +3,7 @@ import { placeSelectionInsideBlock } from '../selection'
|
|||
import { isEqual } from 'lodash'
|
||||
import { FigureData } from '../../figure-modal'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
import { PreviewPath } from '../../../../../../../types/preview-path'
|
||||
|
||||
export class GraphicsWidget extends WidgetType {
|
||||
destroyed = false
|
||||
|
@ -10,9 +11,7 @@ export class GraphicsWidget extends WidgetType {
|
|||
|
||||
constructor(
|
||||
public filePath: string,
|
||||
public getPreviewByPath: (
|
||||
filePath: string
|
||||
) => { url: string; extension: string } | null,
|
||||
public previewByPath: (path: string) => PreviewPath | null,
|
||||
public centered: boolean,
|
||||
public figureData: FigureData | null
|
||||
) {
|
||||
|
@ -82,7 +81,7 @@ export class GraphicsWidget extends WidgetType {
|
|||
renderGraphic(element: HTMLElement, view: EditorView) {
|
||||
element.textContent = '' // ensure the element is empty
|
||||
|
||||
const preview = this.getPreviewByPath(this.filePath)
|
||||
const preview = this.previewByPath(this.filePath)
|
||||
element.dataset.filepath = this.filePath
|
||||
element.dataset.width = this.figureData?.width?.toString()
|
||||
|
||||
|
|
|
@ -24,14 +24,11 @@ import { pasteHtml } from './paste-html'
|
|||
import { commandTooltip } from '../command-tooltip'
|
||||
import { tableGeneratorTheme } from './table-generator'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
import { PreviewPath } from '../../../../../../types/preview-path'
|
||||
|
||||
type Options = {
|
||||
visual: boolean
|
||||
fileTreeManager: {
|
||||
getPreviewByPath: (
|
||||
path: string
|
||||
) => { url: string; extension: string } | null
|
||||
}
|
||||
previewByPath: (path: string) => PreviewPath | null
|
||||
}
|
||||
|
||||
const visualConf = new Compartment()
|
||||
|
|
|
@ -48,6 +48,7 @@ import { CurrentDoc } from '../../../../../types/current-doc'
|
|||
import { useErrorHandler } from 'react-error-boundary'
|
||||
import { setVisual } from '../extensions/visual/visual'
|
||||
import getMeta from '../../../utils/meta'
|
||||
import { useFileTreePathContext } from '@/features/file-tree/contexts/file-tree-path'
|
||||
|
||||
function useCodeMirrorScope(view: EditorView) {
|
||||
const ide = useIdeContext()
|
||||
|
@ -244,8 +245,10 @@ function useCodeMirrorScope(view: EditorView) {
|
|||
|
||||
const editableRef = useRef(permissionsLevel !== 'readOnly')
|
||||
|
||||
const { previewByPath } = useFileTreePathContext()
|
||||
|
||||
const visualRef = useRef({
|
||||
fileTreeManager: ide.fileTreeManager,
|
||||
previewByPath,
|
||||
visual,
|
||||
})
|
||||
|
||||
|
@ -312,6 +315,11 @@ function useCodeMirrorScope(view: EditorView) {
|
|||
window.dispatchEvent(new Event('editor:visual-switch'))
|
||||
}, [view, visual])
|
||||
|
||||
useEffect(() => {
|
||||
visualRef.current.previewByPath = previewByPath
|
||||
view.dispatch(setVisual(visualRef.current))
|
||||
}, [view, previewByPath])
|
||||
|
||||
useEffect(() => {
|
||||
editableRef.current = permissionsLevel !== 'readOnly'
|
||||
view.dispatch(setEditable(editableRef.current)) // the editor needs to be locked when there's a problem saving data
|
||||
|
|
|
@ -50,6 +50,13 @@ export default BinaryFilesManager = class BinaryFilesManager {
|
|||
)
|
||||
}
|
||||
|
||||
openFileWithId(id) {
|
||||
const entity = this.ide.fileTreeManager.findEntityById(id)
|
||||
if (entity?.type === 'file') {
|
||||
this.openFile(entity)
|
||||
}
|
||||
}
|
||||
|
||||
closeFile() {
|
||||
return window.setTimeout(
|
||||
() => {
|
||||
|
|
|
@ -29,6 +29,8 @@ import { useEditorContext } from './editor-context'
|
|||
import { buildFileList } from '../../features/pdf-preview/util/file-list'
|
||||
import { useLayoutContext } from './layout-context'
|
||||
import { useUserContext } from './user-context'
|
||||
import { useFileTreeData } from '@/shared/context/file-tree-data-context'
|
||||
import { useFileTreePathContext } from '@/features/file-tree/contexts/file-tree-path'
|
||||
|
||||
export const LocalCompileContext = createContext()
|
||||
|
||||
|
@ -95,6 +97,9 @@ export function LocalCompileProvider({ children }) {
|
|||
|
||||
const { features } = useUserContext()
|
||||
|
||||
const { fileTreeData } = useFileTreeData()
|
||||
const { findEntityByPath } = useFileTreePathContext()
|
||||
|
||||
// whether a compile is in progress
|
||||
const [compiling, setCompiling] = useState(false)
|
||||
|
||||
|
@ -245,6 +250,17 @@ export function LocalCompileProvider({ children }) {
|
|||
compilingRef.current = compiling
|
||||
}, [compiling])
|
||||
|
||||
const _buildLogEntryAnnotations = useCallback(
|
||||
entries => buildLogEntryAnnotations(entries, fileTreeData, rootDocId),
|
||||
[fileTreeData, rootDocId]
|
||||
)
|
||||
|
||||
const buildLogEntryAnnotationsRef = useRef(_buildLogEntryAnnotations)
|
||||
|
||||
useEffect(() => {
|
||||
buildLogEntryAnnotationsRef.current = _buildLogEntryAnnotations
|
||||
}, [_buildLogEntryAnnotations])
|
||||
|
||||
// the document compiler
|
||||
const [compiler] = useState(() => {
|
||||
return new DocumentCompiler({
|
||||
|
@ -349,10 +365,7 @@ export function LocalCompileProvider({ children }) {
|
|||
setRawLog(result.log)
|
||||
setLogEntries(result.logEntries)
|
||||
setLogEntryAnnotations(
|
||||
buildLogEntryAnnotations(
|
||||
result.logEntries.all,
|
||||
ide.fileTreeManager
|
||||
)
|
||||
buildLogEntryAnnotationsRef.current(result.logEntries.all)
|
||||
)
|
||||
|
||||
// sample compile stats for real users
|
||||
|
@ -521,16 +534,16 @@ export function LocalCompileProvider({ children }) {
|
|||
|
||||
const syncToEntry = useCallback(
|
||||
entry => {
|
||||
const entity = ide.fileTreeManager.findEntityByPath(entry.file)
|
||||
const result = findEntityByPath(entry.file)
|
||||
|
||||
if (entity && entity.type === 'doc') {
|
||||
ide.editorManager.openDoc(entity, {
|
||||
if (result && result.type === 'doc') {
|
||||
ide.editorManager.openDocId(result.entity._id, {
|
||||
gotoLine: entry.line ?? undefined,
|
||||
gotoColumn: entry.column ?? undefined,
|
||||
})
|
||||
}
|
||||
},
|
||||
[ide]
|
||||
[findEntityByPath, ide.editorManager]
|
||||
)
|
||||
|
||||
// clear the cache then run a compile, triggered by a menu item
|
||||
|
|
|
@ -13,6 +13,7 @@ import { ProjectProvider } from './project-context'
|
|||
import { SplitTestProvider } from './split-test-context'
|
||||
import { FileTreeDataProvider } from './file-tree-data-context'
|
||||
import { ProjectSettingsProvider } from '@/features/editor-left-menu/context/project-settings-context'
|
||||
import { FileTreePathProvider } from '@/features/file-tree/contexts/file-tree-path'
|
||||
|
||||
export function ContextRoot({ children, ide }) {
|
||||
return (
|
||||
|
@ -21,6 +22,7 @@ export function ContextRoot({ children, ide }) {
|
|||
<UserProvider>
|
||||
<ProjectProvider>
|
||||
<FileTreeDataProvider>
|
||||
<FileTreePathProvider>
|
||||
<DetachProvider>
|
||||
<EditorProvider>
|
||||
<ProjectSettingsProvider>
|
||||
|
@ -34,6 +36,7 @@ export function ContextRoot({ children, ide }) {
|
|||
</ProjectSettingsProvider>
|
||||
</EditorProvider>
|
||||
</DetachProvider>
|
||||
</FileTreePathProvider>
|
||||
</FileTreeDataProvider>
|
||||
</ProjectProvider>
|
||||
</UserProvider>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { useEffect, useMemo } from 'react'
|
||||
import { get } from 'lodash'
|
||||
import { ContextRoot } from '../../js/shared/context/root-context'
|
||||
import { User } from '../../../types/user'
|
||||
import { Project } from '../../../types/project'
|
||||
import {
|
||||
|
@ -10,6 +9,18 @@ import {
|
|||
} from '../fixtures/compile'
|
||||
import useFetchMock from '../hooks/use-fetch-mock'
|
||||
import { useMeta } from '../hooks/use-meta'
|
||||
import { SplitTestProvider } from '@/shared/context/split-test-context'
|
||||
import { IdeAngularProvider } from '@/shared/context/ide-angular-provider'
|
||||
import { UserProvider } from '@/shared/context/user-context'
|
||||
import { ProjectProvider } from '@/shared/context/project-context'
|
||||
import { FileTreeDataProvider } from '@/shared/context/file-tree-data-context'
|
||||
import { EditorProvider } from '@/shared/context/editor-context'
|
||||
import { DetachProvider } from '@/shared/context/detach-context'
|
||||
import { LayoutProvider } from '@/shared/context/layout-context'
|
||||
import { LocalCompileProvider } from '@/shared/context/local-compile-context'
|
||||
import { DetachCompileProvider } from '@/shared/context/detach-compile-context'
|
||||
import { ProjectSettingsProvider } from '@/features/editor-left-menu/context/project-settings-context'
|
||||
import { FileTreePathProvider } from '@/features/file-tree/contexts/file-tree-path'
|
||||
|
||||
const scopeWatchers: [string, (value: any) => void][] = []
|
||||
|
||||
|
@ -97,19 +108,6 @@ const initialize = () => {
|
|||
on: () => {},
|
||||
removeListener: () => {},
|
||||
},
|
||||
fileTreeManager: {
|
||||
findEntityById: () => null,
|
||||
findEntityByPath: () => null,
|
||||
getEntityPath: () => null,
|
||||
getRootDocDirname: () => undefined,
|
||||
getPreviewByPath: (path: string) =>
|
||||
path === 'frog.jpg'
|
||||
? {
|
||||
extension: 'png',
|
||||
url: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAYAAAB5fY51AAABhWlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AcxV9TpaJVETuIOGSogmBBVMRRq1CECqFWaNXB5NIvaNKQpLg4Cq4FBz8Wqw4uzro6uAqC4AeIq4uToouU+L+k0CLWg+N+vLv3uHsHCNUi06y2cUDTbTMRi4qp9KoYeIWAPnShB6Mys4w5SYqj5fi6h4+vdxGe1frcn6NbzVgM8InEs8wwbeIN4ulN2+C8TxxieVklPiceM+mCxI9cVzx+45xzWeCZITOZmCcOEYu5JlaamOVNjXiKOKxqOuULKY9VzluctWKZ1e/JXxjM6CvLXKc5hBgWsQQJIhSUUUARNiK06qRYSNB+tIV/0PVL5FLIVQAjxwJK0CC7fvA/+N2tlZ2c8JKCUaD9xXE+hoHALlCrOM73sePUTgD/M3ClN/ylKjDzSXqloYWPgN5t4OK6oSl7wOUOMPBkyKbsSn6aQjYLvJ/RN6WB/lugc83rrb6P0wcgSV3Fb4CDQ2AkR9nrLd7d0dzbv2fq/f0ARfNylZJUgMQAAAAGYktHRABuAP8AAGHZRr4AAAAJcEhZcwAALiMAAC4jAXilP3YAAAAHdElNRQfnAhELEhgyPeVkAAAAGXRFWHRDb21tZW50AENyZWF0ZWQgd2l0aCBHSU1QV4EOFwAAAyVJREFUeNrt1rEJgDAURVGVNCmS2hS6PziCteIYWjuEbiEfOWeEV1xe35bt6QhjnlYjBJLzbYRABhMAggUgWIBgAQgWgGABggUgWACCBQgWgGABCBYgWACCBSBYgGABCBaAYAGCBSBYAIIFCBaAYAEIFiBYAIIFIFiAYAEIFiBYAIIFIFiAYAEIFoBgAYIFIFgAggUIFoBgAQgWIFgAggUgWIBgAQgWgGABggUgWACCBQgWgGABCBYgWACCBSBYgGABCBYgWACCBSBYgGABCBaAYAGCBSBYAIIFCBaAYAEIFiBYAIIFIFiAYAEIFoBgAYIFIFgAggUIFoBgAQgWIFgAggUgWIBgAQgWIFgAggUgWIBgAQgWgGABggUgWACCBQgWgGABCBbwX6m13QqB5HwbIZBSLyN4WACCBQgWgGABCBYgWACCBSBYgGABCBaAYAGCBSBYAIIFCBaAYAEIFiBYAIIFCBaAYAEIFiBYAIIFIFiAYAEIFoBgAYIFIFgAggUIFoBgAQgWIFgAggUgWIBgAQgWgGABggUgWACCBQgWgGABCBYgWACCBQgWgGABCBYgWACCBSBYgGABCBaAYAGCBSBYAIIFCBaAYAEIFiBYAIIFIFiAYAEIFoBgAYIFIFgAggUIFoBgAQgWIFgAggUIFoBgAQgWIFgAggUgWIBgAQgWgGABggUgWACCBQgWgGABCBYgWACCBSBYgGABCBaAYAGCBfCFVMtphUBKvYwQSBsPI3hYAIIFCBaAYAEIFiBYAIIFIFiAYAEIFoBgAYIFIFgAggUIFoBgAQgWIFgAggUIFoBgAQgWIFgAggUgWIBgAQgWgGABggUgWACCBQgWgGABCBYgWACCBSBYgGABCBaAYAGCBSBYAIIFCBaAYAEIFiBYAIIFCBaAYAEIFiBYAIIFIFiAYAEIFoBgAYIFIFgAggUIFoBgAQgWIFgAggUgWIBgAQgWgGABggUgWACCBQgWgGABCBYgWACCBQgWgGABCBYgWACCBSBYgGABCBaAYAGCBSBYAIIFCBaAYAEIFiBYAIIFIFiAYAEIFoBgAYIF8IUXjtUMuBMh1xAAAAAASUVORK5CYII=',
|
||||
}
|
||||
: null,
|
||||
},
|
||||
editorManager: {
|
||||
getCurrentDocId: () => 'foo',
|
||||
openDoc: (id: string, options: unknown) => {
|
||||
|
@ -207,6 +205,7 @@ const initialize = () => {
|
|||
|
||||
type ScopeDecoratorOptions = {
|
||||
mockCompileOnLoad: boolean
|
||||
providers?: Record<string, any>
|
||||
}
|
||||
|
||||
export const ScopeDecorator = (
|
||||
|
@ -237,9 +236,47 @@ export const ScopeDecorator = (
|
|||
// set values on window.metaAttributesCache (created in initialize, above)
|
||||
useMeta(meta)
|
||||
|
||||
const Providers = {
|
||||
DetachCompileProvider,
|
||||
DetachProvider,
|
||||
EditorProvider,
|
||||
FileTreeDataProvider,
|
||||
FileTreePathProvider,
|
||||
IdeAngularProvider,
|
||||
LayoutProvider,
|
||||
LocalCompileProvider,
|
||||
ProjectProvider,
|
||||
ProjectSettingsProvider,
|
||||
SplitTestProvider,
|
||||
UserProvider,
|
||||
...opts.providers,
|
||||
}
|
||||
|
||||
return (
|
||||
<ContextRoot ide={ide}>
|
||||
<Providers.SplitTestProvider>
|
||||
<Providers.IdeAngularProvider ide={ide}>
|
||||
<Providers.UserProvider>
|
||||
<Providers.ProjectProvider>
|
||||
<Providers.FileTreeDataProvider>
|
||||
<Providers.FileTreePathProvider>
|
||||
<Providers.DetachProvider>
|
||||
<Providers.EditorProvider>
|
||||
<Providers.ProjectSettingsProvider>
|
||||
<Providers.LayoutProvider>
|
||||
<Providers.LocalCompileProvider>
|
||||
<Providers.DetachCompileProvider>
|
||||
<Story />
|
||||
</ContextRoot>
|
||||
</Providers.DetachCompileProvider>
|
||||
</Providers.LocalCompileProvider>
|
||||
</Providers.LayoutProvider>
|
||||
</Providers.ProjectSettingsProvider>
|
||||
</Providers.EditorProvider>
|
||||
</Providers.DetachProvider>
|
||||
</Providers.FileTreePathProvider>
|
||||
</Providers.FileTreeDataProvider>
|
||||
</Providers.ProjectProvider>
|
||||
</Providers.UserProvider>
|
||||
</Providers.IdeAngularProvider>
|
||||
</Providers.SplitTestProvider>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -2,12 +2,37 @@ import SourceEditor from '../../js/features/source-editor/components/source-edit
|
|||
import { ScopeDecorator } from '../decorators/scope'
|
||||
import { useScope } from '../hooks/use-scope'
|
||||
import { useMeta } from '../hooks/use-meta'
|
||||
import { FC } from 'react'
|
||||
import { FileTreePathContext } from '@/features/file-tree/contexts/file-tree-path'
|
||||
|
||||
const FileTreePathProvider: FC = ({ children }) => (
|
||||
<FileTreePathContext.Provider
|
||||
value={{
|
||||
dirname: () => null,
|
||||
findEntityByPath: () => null,
|
||||
pathInFolder: () => null,
|
||||
previewByPath: (path: string) =>
|
||||
path === 'frog.jpg'
|
||||
? {
|
||||
extension: 'png',
|
||||
url: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAYAAAB5fY51AAABhWlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AcxV9TpaJVETuIOGSogmBBVMRRq1CECqFWaNXB5NIvaNKQpLg4Cq4FBz8Wqw4uzro6uAqC4AeIq4uToouU+L+k0CLWg+N+vLv3uHsHCNUi06y2cUDTbTMRi4qp9KoYeIWAPnShB6Mys4w5SYqj5fi6h4+vdxGe1frcn6NbzVgM8InEs8wwbeIN4ulN2+C8TxxieVklPiceM+mCxI9cVzx+45xzWeCZITOZmCcOEYu5JlaamOVNjXiKOKxqOuULKY9VzluctWKZ1e/JXxjM6CvLXKc5hBgWsQQJIhSUUUARNiK06qRYSNB+tIV/0PVL5FLIVQAjxwJK0CC7fvA/+N2tlZ2c8JKCUaD9xXE+hoHALlCrOM73sePUTgD/M3ClN/ylKjDzSXqloYWPgN5t4OK6oSl7wOUOMPBkyKbsSn6aQjYLvJ/RN6WB/lugc83rrb6P0wcgSV3Fb4CDQ2AkR9nrLd7d0dzbv2fq/f0ARfNylZJUgMQAAAAGYktHRABuAP8AAGHZRr4AAAAJcEhZcwAALiMAAC4jAXilP3YAAAAHdElNRQfnAhELEhgyPeVkAAAAGXRFWHRDb21tZW50AENyZWF0ZWQgd2l0aCBHSU1QV4EOFwAAAyVJREFUeNrt1rEJgDAURVGVNCmS2hS6PziCteIYWjuEbiEfOWeEV1xe35bt6QhjnlYjBJLzbYRABhMAggUgWIBgAQgWgGABggUgWACCBQgWgGABCBYgWACCBSBYgGABCBaAYAGCBSBYAIIFCBaAYAEIFiBYAIIFIFiAYAEIFiBYAIIFIFiAYAEIFoBgAYIFIFgAggUIFoBgAQgWIFgAggUgWIBgAQgWgGABggUgWACCBQgWgGABCBYgWACCBSBYgGABCBYgWACCBSBYgGABCBaAYAGCBSBYAIIFCBaAYAEIFiBYAIIFIFiAYAEIFoBgAYIFIFgAggUIFoBgAQgWIFgAggUgWIBgAQgWIFgAggUgWIBgAQgWgGABggUgWACCBQgWgGABCBbwX6m13QqB5HwbIZBSLyN4WACCBQgWgGABCBYgWACCBSBYgGABCBaAYAGCBSBYAIIFCBaAYAEIFiBYAIIFCBaAYAEIFiBYAIIFIFiAYAEIFoBgAYIFIFgAggUIFoBgAQgWIFgAggUgWIBgAQgWgGABggUgWACCBQgWgGABCBYgWACCBQgWgGABCBYgWACCBSBYgGABCBaAYAGCBSBYAIIFCBaAYAEIFiBYAIIFIFiAYAEIFoBgAYIFIFgAggUIFoBgAQgWIFgAggUIFoBgAQgWIFgAggUgWIBgAQgWgGABggUgWACCBQgWgGABCBYgWACCBSBYgGABCBaAYAGCBfCFVMtphUBKvYwQSBsPI3hYAIIFCBaAYAEIFiBYAIIFIFiAYAEIFoBgAYIFIFgAggUIFoBgAQgWIFgAggUIFoBgAQgWIFgAggUgWIBgAQgWgGABggUgWACCBQgWgGABCBYgWACCBSBYgGABCBaAYAGCBSBYAIIFCBaAYAEIFiBYAIIFCBaAYAEIFiBYAIIFIFiAYAEIFoBgAYIFIFgAggUIFoBgAQgWIFgAggUgWIBgAQgWgGABggUgWACCBQgWgGABCBYgWACCBQgWgGABCBYgWACCBSBYgGABCBaAYAGCBSBYAIIFCBaAYAEIFiBYAIIFIFiAYAEIFoBgAYIF8IUXjtUMuBMh1xAAAAAASUVORK5CYII=',
|
||||
}
|
||||
: null,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</FileTreePathContext.Provider>
|
||||
)
|
||||
|
||||
export default {
|
||||
title: 'Editor / Source Editor',
|
||||
component: SourceEditor,
|
||||
decorators: [
|
||||
ScopeDecorator,
|
||||
(Story: any) =>
|
||||
ScopeDecorator(Story, {
|
||||
mockCompileOnLoad: true,
|
||||
providers: { FileTreePathProvider },
|
||||
}),
|
||||
(Story: any) => (
|
||||
<div style={{ height: '90vh' }}>
|
||||
<Story />
|
||||
|
@ -93,29 +118,6 @@ export const Visual = (args: any, { globals: { theme } }: any) => {
|
|||
open_doc_name: 'example.tex',
|
||||
showVisual: true,
|
||||
},
|
||||
rootFolder: {
|
||||
name: 'rootFolder',
|
||||
id: 'root-folder-id',
|
||||
type: 'folder',
|
||||
children: [
|
||||
{
|
||||
name: 'example.tex.tex',
|
||||
id: 'example-doc-id',
|
||||
type: 'doc',
|
||||
selected: false,
|
||||
$$hashKey: 'object:89',
|
||||
},
|
||||
{
|
||||
name: 'frog.jpg',
|
||||
id: 'frog-image-id',
|
||||
type: 'file',
|
||||
linkedFileData: null,
|
||||
created: '2023-05-04T16:11:04.352Z',
|
||||
$$hashKey: 'object:108',
|
||||
},
|
||||
],
|
||||
selected: false,
|
||||
},
|
||||
settings: {
|
||||
...settings,
|
||||
overallTheme: theme === 'default-' ? '' : theme,
|
||||
|
@ -127,6 +129,7 @@ export const Visual = (args: any, { globals: { theme } }: any) => {
|
|||
'ol-completedTutorials': {
|
||||
'table-generator-promotion': '2023-09-01T00:00:00.000Z',
|
||||
},
|
||||
'ol-project_id': '63e21c07946dd8c76505f85a',
|
||||
})
|
||||
|
||||
return <SourceEditor />
|
||||
|
|
|
@ -1,9 +1,31 @@
|
|||
import { EditorProviders } from '../../helpers/editor-providers'
|
||||
import PdfLogsEntries from '../../../../frontend/js/features/pdf-preview/components/pdf-logs-entries'
|
||||
import { detachChannel, testDetachChannel } from '../../helpers/detach-channel'
|
||||
import { FileTreePathContext } from '@/features/file-tree/contexts/file-tree-path'
|
||||
import { FindResult } from '@/features/file-tree/util/path'
|
||||
import { FC } from 'react'
|
||||
|
||||
describe('<PdfLogsEntries/>', function () {
|
||||
const fakeEntity = { type: 'doc' }
|
||||
const fakeFindEntityResult: FindResult = {
|
||||
type: 'doc',
|
||||
entity: { _id: '123', name: '123 Doc' },
|
||||
}
|
||||
|
||||
const FileTreePathProvider: FC = ({ children }) => (
|
||||
<FileTreePathContext.Provider
|
||||
value={{
|
||||
dirname: cy.stub(),
|
||||
findEntityByPath: cy
|
||||
.stub()
|
||||
.as('findEntityByPath')
|
||||
.returns(fakeFindEntityResult),
|
||||
pathInFolder: cy.stub(),
|
||||
previewByPath: cy.stub(),
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</FileTreePathContext.Provider>
|
||||
)
|
||||
|
||||
const logEntries = [
|
||||
{
|
||||
|
@ -23,11 +45,8 @@ describe('<PdfLogsEntries/>', function () {
|
|||
|
||||
beforeEach(function () {
|
||||
props = {
|
||||
fileTreeManager: {
|
||||
findEntityByPath: cy.stub().as('findEntityByPath').returns(fakeEntity),
|
||||
},
|
||||
editorManager: {
|
||||
openDoc: cy.spy().as('openDoc'),
|
||||
openDocId: cy.spy().as('openDocId'),
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -47,7 +66,7 @@ describe('<PdfLogsEntries/>', function () {
|
|||
|
||||
it('opens doc on click', function () {
|
||||
cy.mount(
|
||||
<EditorProviders {...props}>
|
||||
<EditorProviders {...props} providers={{ FileTreePathProvider }}>
|
||||
<PdfLogsEntries entries={logEntries} />
|
||||
</EditorProviders>
|
||||
)
|
||||
|
@ -57,10 +76,14 @@ describe('<PdfLogsEntries/>', function () {
|
|||
}).click()
|
||||
|
||||
cy.get('@findEntityByPath').should('have.been.calledOnce')
|
||||
cy.get('@openDoc').should('have.been.calledOnceWith', fakeEntity, {
|
||||
cy.get('@openDocId').should(
|
||||
'have.been.calledOnceWith',
|
||||
fakeFindEntityResult.entity._id,
|
||||
{
|
||||
gotoLine: 9,
|
||||
gotoColumn: 8,
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('opens doc via detached action', function () {
|
||||
|
@ -69,7 +92,7 @@ describe('<PdfLogsEntries/>', function () {
|
|||
})
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders {...props}>
|
||||
<EditorProviders {...props} providers={{ FileTreePathProvider }}>
|
||||
<PdfLogsEntries entries={logEntries} />
|
||||
</EditorProviders>
|
||||
).then(() => {
|
||||
|
@ -89,10 +112,14 @@ describe('<PdfLogsEntries/>', function () {
|
|||
})
|
||||
|
||||
cy.get('@findEntityByPath').should('have.been.calledOnce')
|
||||
cy.get('@openDoc').should('have.been.calledOnceWith', fakeEntity, {
|
||||
cy.get('@openDocId').should(
|
||||
'have.been.calledOnceWith',
|
||||
fakeFindEntityResult.entity._id,
|
||||
{
|
||||
gotoLine: 7,
|
||||
gotoColumn: 6,
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('sends open doc clicks via detached action', function () {
|
||||
|
@ -101,7 +128,7 @@ describe('<PdfLogsEntries/>', function () {
|
|||
})
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders {...props}>
|
||||
<EditorProviders {...props} providers={{ FileTreePathProvider }}>
|
||||
<PdfLogsEntries entries={logEntries} />
|
||||
</EditorProviders>
|
||||
)
|
||||
|
@ -113,7 +140,7 @@ describe('<PdfLogsEntries/>', function () {
|
|||
}).click()
|
||||
|
||||
cy.get('@findEntityByPath').should('not.have.been.called')
|
||||
cy.get('@openDoc').should('not.have.been.called')
|
||||
cy.get('@openDocId').should('not.have.been.called')
|
||||
cy.get('@postDetachMessage').should('have.been.calledWith', {
|
||||
role: 'detached',
|
||||
event: 'action-sync-to-entry',
|
||||
|
|
|
@ -1,20 +1,24 @@
|
|||
import { mockScope } from '../helpers/mock-scope'
|
||||
import { EditorProviders } from '../../../helpers/editor-providers'
|
||||
import CodemirrorEditor from '../../../../../frontend/js/features/source-editor/components/codemirror-editor'
|
||||
import { FC } from 'react'
|
||||
import { FC, ComponentProps } from 'react'
|
||||
import { FileTreePathContext } from '@/features/file-tree/contexts/file-tree-path'
|
||||
|
||||
const Container: FC = ({ children }) => (
|
||||
<div style={{ width: 785, height: 785 }}>{children}</div>
|
||||
)
|
||||
|
||||
const mountEditor = (content: string, ...args: any[]) => {
|
||||
const mountEditor = (
|
||||
content: string,
|
||||
props?: Omit<ComponentProps<typeof EditorProviders>, 'children' | 'scope'>
|
||||
) => {
|
||||
const scope = mockScope(content)
|
||||
scope.permissionsLevel = 'readOnly'
|
||||
scope.editor.showVisual = true
|
||||
|
||||
cy.mount(
|
||||
<Container>
|
||||
<EditorProviders scope={scope} {...args}>
|
||||
<EditorProviders scope={scope} {...props}>
|
||||
<CodemirrorEditor />
|
||||
</EditorProviders>
|
||||
</Container>
|
||||
|
@ -81,15 +85,21 @@ describe('<CodeMirrorEditor/> in Visual mode with read-only permission', functio
|
|||
})
|
||||
|
||||
it('does not display the figure edit button', function () {
|
||||
const fileTreeManager = {
|
||||
findEntityById: cy.stub(),
|
||||
const FileTreePathProvider: FC = ({ children }) => (
|
||||
<FileTreePathContext.Provider
|
||||
value={{
|
||||
dirname: cy.stub(),
|
||||
findEntityByPath: cy.stub(),
|
||||
getEntityPath: cy.stub(),
|
||||
getRootDocDirname: cy.stub(),
|
||||
getPreviewByPath: cy
|
||||
pathInFolder: cy.stub(),
|
||||
previewByPath: cy
|
||||
.stub()
|
||||
.as('previewByPath')
|
||||
.returns({ url: '/images/frog.jpg', extension: 'jpg' }),
|
||||
}
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</FileTreePathContext.Provider>
|
||||
)
|
||||
|
||||
cy.intercept('/images/frog.jpg', { fixture: 'images/gradient.png' })
|
||||
|
||||
|
@ -100,7 +110,9 @@ describe('<CodeMirrorEditor/> in Visual mode with read-only permission', functio
|
|||
\\caption{My caption}
|
||||
\\label{fig:my-label}
|
||||
\\end{figure}`,
|
||||
{ fileTreeManager }
|
||||
{
|
||||
providers: { FileTreePathProvider },
|
||||
}
|
||||
)
|
||||
|
||||
cy.get('img.ol-cm-graphics').should('have.length', 1)
|
||||
|
|
|
@ -5,6 +5,7 @@ import { EditorProviders } from '../../../helpers/editor-providers'
|
|||
import CodemirrorEditor from '../../../../../frontend/js/features/source-editor/components/codemirror-editor'
|
||||
import { mockScope } from '../helpers/mock-scope'
|
||||
import forEach from 'mocha-each'
|
||||
import { FileTreePathContext } from '@/features/file-tree/contexts/file-tree-path'
|
||||
|
||||
const Container: FC = ({ children }) => (
|
||||
<div style={{ width: 785, height: 785 }}>{children}</div>
|
||||
|
@ -23,9 +24,25 @@ describe('<CodeMirrorEditor/> in Visual mode', function () {
|
|||
const scope = mockScope(content)
|
||||
scope.editor.showVisual = true
|
||||
|
||||
const FileTreePathProvider: FC = ({ children }) => (
|
||||
<FileTreePathContext.Provider
|
||||
value={{
|
||||
dirname: cy.stub(),
|
||||
findEntityByPath: cy.stub(),
|
||||
pathInFolder: cy.stub(),
|
||||
previewByPath: cy
|
||||
.stub()
|
||||
.as('previewByPath')
|
||||
.callsFake(path => ({ url: path, extension: 'png' })),
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</FileTreePathContext.Provider>
|
||||
)
|
||||
|
||||
cy.mount(
|
||||
<Container>
|
||||
<EditorProviders scope={scope}>
|
||||
<EditorProviders scope={scope} providers={{ FileTreePathProvider }}>
|
||||
<CodemirrorEditor />
|
||||
</EditorProviders>
|
||||
</Container>
|
||||
|
|
|
@ -2,6 +2,7 @@ import CodemirrorEditor from '../../../../../frontend/js/features/source-editor/
|
|||
import { EditorProviders } from '../../../helpers/editor-providers'
|
||||
import { mockScope, rootFolderId } from '../helpers/mock-scope'
|
||||
import { FC } from 'react'
|
||||
import { FileTreePathContext } from '@/features/file-tree/contexts/file-tree-path'
|
||||
|
||||
const Container: FC = ({ children }) => (
|
||||
<div style={{ width: 1500, height: 785 }}>{children}</div>
|
||||
|
@ -50,9 +51,25 @@ describe('<FigureModal />', function () {
|
|||
const scope = mockScope(content)
|
||||
scope.editor.showVisual = true
|
||||
|
||||
const FileTreePathProvider: FC = ({ children }) => (
|
||||
<FileTreePathContext.Provider
|
||||
value={{
|
||||
dirname: cy.stub(),
|
||||
findEntityByPath: cy.stub(),
|
||||
pathInFolder: cy.stub(),
|
||||
previewByPath: cy
|
||||
.stub()
|
||||
.as('previewByPath')
|
||||
.returns({ url: 'frog.jpg', extension: 'jpg' }),
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</FileTreePathContext.Provider>
|
||||
)
|
||||
|
||||
cy.mount(
|
||||
<Container>
|
||||
<EditorProviders scope={scope}>
|
||||
<EditorProviders scope={scope} providers={{ FileTreePathProvider }}>
|
||||
<CodemirrorEditor />
|
||||
</EditorProviders>
|
||||
</Container>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Disable prop type checks for test harnesses
|
||||
/* eslint-disable react/prop-types */
|
||||
import sinon from 'sinon'
|
||||
import { get } from 'lodash'
|
||||
import { get, merge } from 'lodash'
|
||||
import { SplitTestProvider } from '@/shared/context/split-test-context'
|
||||
import { IdeAngularProvider } from '@/shared/context/ide-angular-provider'
|
||||
import { UserProvider } from '@/shared/context/user-context'
|
||||
|
@ -13,6 +13,7 @@ import { LayoutProvider } from '@/shared/context/layout-context'
|
|||
import { LocalCompileProvider } from '@/shared/context/local-compile-context'
|
||||
import { DetachCompileProvider } from '@/shared/context/detach-compile-context'
|
||||
import { ProjectSettingsProvider } from '@/features/editor-left-menu/context/project-settings-context'
|
||||
import { FileTreePathProvider } from '@/features/file-tree/contexts/file-tree-path'
|
||||
|
||||
// these constants can be imported in tests instead of
|
||||
// using magic strings
|
||||
|
@ -70,13 +71,15 @@ export function EditorProviders({
|
|||
},
|
||||
},
|
||||
},
|
||||
providers = {},
|
||||
}) {
|
||||
window.user = user || window.user
|
||||
window.gitBridgePublicBaseUrl = 'https://git.overleaf.test'
|
||||
window.project_id = projectId != null ? projectId : window.project_id
|
||||
window.isRestrictedTokenMember = isRestrictedTokenMember
|
||||
|
||||
const $scope = {
|
||||
const $scope = merge(
|
||||
{
|
||||
user: window.user,
|
||||
project: {
|
||||
_id: window.project_id,
|
||||
|
@ -95,8 +98,9 @@ export function EditorProviders({
|
|||
$applyAsync: sinon.stub(),
|
||||
toggleHistory: sinon.stub(),
|
||||
permissionsLevel,
|
||||
...scope,
|
||||
}
|
||||
},
|
||||
scope
|
||||
)
|
||||
|
||||
window._ide = {
|
||||
$scope,
|
||||
|
@ -109,30 +113,47 @@ export function EditorProviders({
|
|||
|
||||
// Add details for useUserContext
|
||||
window.metaAttributesCache.set('ol-user', { ...user, features })
|
||||
const Providers = {
|
||||
DetachCompileProvider,
|
||||
DetachProvider,
|
||||
EditorProvider,
|
||||
FileTreeDataProvider,
|
||||
FileTreePathProvider,
|
||||
IdeAngularProvider,
|
||||
LayoutProvider,
|
||||
LocalCompileProvider,
|
||||
ProjectProvider,
|
||||
ProjectSettingsProvider,
|
||||
SplitTestProvider,
|
||||
UserProvider,
|
||||
...providers,
|
||||
}
|
||||
|
||||
return (
|
||||
<SplitTestProvider>
|
||||
<IdeAngularProvider ide={window._ide}>
|
||||
<UserProvider>
|
||||
<ProjectProvider>
|
||||
<FileTreeDataProvider>
|
||||
<DetachProvider>
|
||||
<EditorProvider>
|
||||
<ProjectSettingsProvider>
|
||||
<LayoutProvider>
|
||||
<LocalCompileProvider>
|
||||
<DetachCompileProvider>
|
||||
<Providers.SplitTestProvider>
|
||||
<Providers.IdeAngularProvider ide={window._ide}>
|
||||
<Providers.UserProvider>
|
||||
<Providers.ProjectProvider>
|
||||
<Providers.FileTreeDataProvider>
|
||||
<Providers.FileTreePathProvider>
|
||||
<Providers.DetachProvider>
|
||||
<Providers.EditorProvider>
|
||||
<Providers.ProjectSettingsProvider>
|
||||
<Providers.LayoutProvider>
|
||||
<Providers.LocalCompileProvider>
|
||||
<Providers.DetachCompileProvider>
|
||||
{children}
|
||||
</DetachCompileProvider>
|
||||
</LocalCompileProvider>
|
||||
</LayoutProvider>
|
||||
</ProjectSettingsProvider>
|
||||
</EditorProvider>
|
||||
</DetachProvider>
|
||||
</FileTreeDataProvider>
|
||||
</ProjectProvider>
|
||||
</UserProvider>
|
||||
</IdeAngularProvider>
|
||||
</SplitTestProvider>
|
||||
</Providers.DetachCompileProvider>
|
||||
</Providers.LocalCompileProvider>
|
||||
</Providers.LayoutProvider>
|
||||
</Providers.ProjectSettingsProvider>
|
||||
</Providers.EditorProvider>
|
||||
</Providers.DetachProvider>
|
||||
</Providers.FileTreePathProvider>
|
||||
</Providers.FileTreeDataProvider>
|
||||
</Providers.ProjectProvider>
|
||||
</Providers.UserProvider>
|
||||
</Providers.IdeAngularProvider>
|
||||
</Providers.SplitTestProvider>
|
||||
)
|
||||
}
|
||||
|
|
5
services/web/types/file-tree-entity.ts
Normal file
5
services/web/types/file-tree-entity.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { Folder } from './folder'
|
||||
import { Doc } from './doc'
|
||||
import { FileRef } from './file-ref'
|
||||
|
||||
export type FileTreeEntity = Folder | Doc | FileRef
|
4
services/web/types/preview-path.ts
Normal file
4
services/web/types/preview-path.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export type PreviewPath = {
|
||||
url: string
|
||||
extension: string
|
||||
}
|
Loading…
Reference in a new issue