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) {
|
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
|
// 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
|
// mutationFunction we pass an empty array as the parent and return the
|
||||||
// mutated tree directly
|
// 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 { sendMB } from '../../../../infrastructure/event-tracking'
|
||||||
import { useIdeContext } from '../../../../shared/context/ide-context'
|
import { useIdeContext } from '../../../../shared/context/ide-context'
|
||||||
import { useLayoutContext } from '../../../../shared/context/layout-context'
|
import { useLayoutContext } from '../../../../shared/context/layout-context'
|
||||||
import useAsync from '../../../../shared/hooks/use-async'
|
|
||||||
import { restoreFile } from '../../services/api'
|
import { restoreFile } from '../../services/api'
|
||||||
import { isFileRemoved } from '../../utils/file-diff'
|
import { isFileRemoved } from '../../utils/file-diff'
|
||||||
import { waitFor } from '../../utils/wait-for'
|
|
||||||
import { useHistoryContext } from '../history-context'
|
import { useHistoryContext } from '../history-context'
|
||||||
import type { HistoryContextValue } from '../types/history-context-value'
|
import type { HistoryContextValue } from '../types/history-context-value'
|
||||||
import { useErrorHandler } from 'react-error-boundary'
|
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() {
|
export function useRestoreDeletedFile() {
|
||||||
const { isLoading, runAsync } = useAsync()
|
|
||||||
const { projectId } = useHistoryContext()
|
const { projectId } = useHistoryContext()
|
||||||
const ide = useIdeContext()
|
const ide = useIdeContext()
|
||||||
const { setView } = useLayoutContext()
|
const { setView } = useLayoutContext()
|
||||||
const handleError = useErrorHandler()
|
const handleError = useErrorHandler()
|
||||||
|
const { fileTreeData } = useFileTreeData()
|
||||||
|
const [state, setState] = useState<RestorationState>('idle')
|
||||||
|
const [restoredFileMetadata, setRestoredFileMetadata] =
|
||||||
|
useState<RestoreFileResponse | null>(null)
|
||||||
|
|
||||||
const restoreDeletedFile = async (
|
const isLoading = state === 'restoring' || state === 'waitingForFileTree'
|
||||||
selection: HistoryContextValue['selection']
|
|
||||||
) => {
|
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
|
const { selectedFile } = selection
|
||||||
|
|
||||||
if (selectedFile && selectedFile.pathname && isFileRemoved(selectedFile)) {
|
if (
|
||||||
|
selectedFile &&
|
||||||
|
selectedFile.pathname &&
|
||||||
|
isFileRemoved(selectedFile)
|
||||||
|
) {
|
||||||
sendMB('history-v2-restore-deleted')
|
sendMB('history-v2-restore-deleted')
|
||||||
|
|
||||||
await runAsync(
|
setState('restoring')
|
||||||
restoreFile(projectId, selectedFile)
|
restoreFile(projectId, selectedFile).then(
|
||||||
.then(async data => {
|
(data: RestoreFileResponse) => {
|
||||||
const { id, type } = data
|
setRestoredFileMetadata(data)
|
||||||
|
setState('waitingForFileTree')
|
||||||
const entity = await waitFor(
|
},
|
||||||
() => ide.fileTreeManager.findEntityById(id),
|
error => {
|
||||||
3000
|
setState('error')
|
||||||
)
|
handleError(error)
|
||||||
|
|
||||||
if (type === 'doc') {
|
|
||||||
ide.editorManager.openDoc(entity)
|
|
||||||
} else {
|
|
||||||
ide.binaryFilesManager.openFile(entity)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setView('editor')
|
|
||||||
})
|
|
||||||
.catch(handleError)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
[handleError, projectId]
|
||||||
|
)
|
||||||
|
|
||||||
return { restoreDeletedFile, isLoading }
|
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 useScopeEventListener from '../../../shared/hooks/use-scope-event-listener'
|
||||||
import * as eventTracking from '../../../infrastructure/event-tracking'
|
import * as eventTracking from '../../../infrastructure/event-tracking'
|
||||||
import { debugConsole } from '@/utils/debugging'
|
import { debugConsole } from '@/utils/debugging'
|
||||||
|
import { useFileTreePathContext } from '@/features/file-tree/contexts/file-tree-path'
|
||||||
|
|
||||||
function GoToCodeButton({
|
function GoToCodeButton({
|
||||||
position,
|
position,
|
||||||
|
@ -118,7 +119,7 @@ function GoToPdfButton({
|
||||||
function PdfSynctexControls() {
|
function PdfSynctexControls() {
|
||||||
const ide = useIdeContext()
|
const ide = useIdeContext()
|
||||||
|
|
||||||
const { _id: projectId } = useProjectContext()
|
const { _id: projectId, rootDocId } = useProjectContext()
|
||||||
|
|
||||||
const { detachRole } = useLayoutContext()
|
const { detachRole } = useLayoutContext()
|
||||||
|
|
||||||
|
@ -132,6 +133,7 @@ function PdfSynctexControls() {
|
||||||
} = useCompileContext()
|
} = useCompileContext()
|
||||||
|
|
||||||
const { selectedEntities } = useFileTreeData()
|
const { selectedEntities } = useFileTreeData()
|
||||||
|
const { findEntityByPath, dirname, pathInFolder } = useFileTreePathContext()
|
||||||
|
|
||||||
const [cursorPosition, setCursorPosition] = useState(() => {
|
const [cursorPosition, setCursorPosition] = useState(() => {
|
||||||
const position = localStorage.getItem(
|
const position = localStorage.getItem(
|
||||||
|
@ -162,26 +164,28 @@ function PdfSynctexControls() {
|
||||||
|
|
||||||
const getCurrentFilePath = useCallback(() => {
|
const getCurrentFilePath = useCallback(() => {
|
||||||
const docId = ide.editorManager.getCurrentDocId()
|
const docId = ide.editorManager.getCurrentDocId()
|
||||||
const doc = ide.fileTreeManager.findEntityById(docId)
|
let path = pathInFolder(docId)
|
||||||
|
|
||||||
let path = ide.fileTreeManager.getEntityPath(doc)
|
|
||||||
|
|
||||||
// If the root file is folder/main.tex, then synctex sees the path as folder/./main.tex
|
// 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) {
|
if (rootDocDirname) {
|
||||||
path = path.replace(RegExp(`^${rootDocDirname}`), `${rootDocDirname}/.`)
|
path = path.replace(RegExp(`^${rootDocDirname}`), `${rootDocDirname}/.`)
|
||||||
}
|
}
|
||||||
|
|
||||||
return path
|
return path
|
||||||
}, [ide])
|
}, [dirname, ide.editorManager, pathInFolder, rootDocId])
|
||||||
|
|
||||||
const goToCodeLine = useCallback(
|
const goToCodeLine = useCallback(
|
||||||
(file, line) => {
|
(file, line) => {
|
||||||
if (file) {
|
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,
|
gotoLine: line,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
@ -194,7 +198,7 @@ function PdfSynctexControls() {
|
||||||
}, 4000)
|
}, 4000)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[ide, isMounted, setSynctexError]
|
[findEntityByPath, ide.editorManager, isMounted, setSynctexError]
|
||||||
)
|
)
|
||||||
|
|
||||||
const goToPdfLocation = useCallback(
|
const goToPdfLocation = useCallback(
|
||||||
|
|
|
@ -4,6 +4,7 @@ import BibLogParser from '../../../ide/log-parser/bib-log-parser'
|
||||||
import { v4 as uuid } from 'uuid'
|
import { v4 as uuid } from 'uuid'
|
||||||
import { enablePdfCaching } from './pdf-caching-flags'
|
import { enablePdfCaching } from './pdf-caching-flags'
|
||||||
import { debugConsole } from '@/utils/debugging'
|
import { debugConsole } from '@/utils/debugging'
|
||||||
|
import { dirname, findEntityByPath } from '@/features/file-tree/util/path'
|
||||||
|
|
||||||
// Warnings that may disappear after a second LaTeX pass
|
// Warnings that may disappear after a second LaTeX pass
|
||||||
const TRANSIENT_WARNING_REGEX = /^(Reference|Citation).+undefined on input line/
|
const TRANSIENT_WARNING_REGEX = /^(Reference|Citation).+undefined on input line/
|
||||||
|
@ -133,8 +134,8 @@ export const handleLogFiles = async (outputFiles, data, signal) => {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildLogEntryAnnotations(entries, fileTreeManager) {
|
export function buildLogEntryAnnotations(entries, fileTreeData, rootDocId) {
|
||||||
const rootDocDirname = fileTreeManager.getRootDocDirname()
|
const rootDocDirname = dirname(fileTreeData, rootDocId)
|
||||||
|
|
||||||
const logEntryAnnotations = {}
|
const logEntryAnnotations = {}
|
||||||
|
|
||||||
|
@ -142,14 +143,14 @@ export function buildLogEntryAnnotations(entries, fileTreeManager) {
|
||||||
if (entry.file) {
|
if (entry.file) {
|
||||||
entry.file = normalizeFilePath(entry.file, rootDocDirname)
|
entry.file = normalizeFilePath(entry.file, rootDocDirname)
|
||||||
|
|
||||||
const entity = fileTreeManager.findEntityByPath(entry.file)
|
const entity = findEntityByPath(fileTreeData, entry.file)?.entity
|
||||||
|
|
||||||
if (entity) {
|
if (entity) {
|
||||||
if (!(entity.id in logEntryAnnotations)) {
|
if (!(entity._id in logEntryAnnotations)) {
|
||||||
logEntryAnnotations[entity.id] = []
|
logEntryAnnotations[entity._id] = []
|
||||||
}
|
}
|
||||||
|
|
||||||
logEntryAnnotations[entity.id].push({
|
logEntryAnnotations[entity._id].push({
|
||||||
row: entry.line - 1,
|
row: entry.line - 1,
|
||||||
type: entry.level === 'error' ? 'error' : 'warning',
|
type: entry.level === 'error' ? 'error' : 'warning',
|
||||||
text: entry.message,
|
text: entry.message,
|
||||||
|
|
|
@ -66,13 +66,10 @@ import { skipPreambleWithCursor } from './skip-preamble-cursor'
|
||||||
import { TableRenderingErrorWidget } from './visual-widgets/table-rendering-error'
|
import { TableRenderingErrorWidget } from './visual-widgets/table-rendering-error'
|
||||||
import { GraphicsWidget } from './visual-widgets/graphics'
|
import { GraphicsWidget } from './visual-widgets/graphics'
|
||||||
import { InlineGraphicsWidget } from './visual-widgets/inline-graphics'
|
import { InlineGraphicsWidget } from './visual-widgets/inline-graphics'
|
||||||
|
import { PreviewPath } from '../../../../../../types/preview-path'
|
||||||
|
|
||||||
type Options = {
|
type Options = {
|
||||||
fileTreeManager: {
|
previewByPath: (path: string) => PreviewPath | null
|
||||||
getPreviewByPath: (
|
|
||||||
path: string
|
|
||||||
) => { url: string; extension: string } | null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function shouldDecorate(
|
function shouldDecorate(
|
||||||
|
@ -135,9 +132,7 @@ const hasClosingBrace = (node: SyntaxNode) =>
|
||||||
* Decorations that span multiple lines must be contained in a StateField, not a ViewPlugin.
|
* Decorations that span multiple lines must be contained in a StateField, not a ViewPlugin.
|
||||||
*/
|
*/
|
||||||
export const atomicDecorations = (options: Options) => {
|
export const atomicDecorations = (options: Options) => {
|
||||||
const getPreviewByPath = (path: string) =>
|
const { previewByPath } = options
|
||||||
options.fileTreeManager.getPreviewByPath(path)
|
|
||||||
|
|
||||||
const createDecorations = (
|
const createDecorations = (
|
||||||
state: EditorState,
|
state: EditorState,
|
||||||
tree: Tree
|
tree: Tree
|
||||||
|
@ -895,7 +890,7 @@ export const atomicDecorations = (options: Options) => {
|
||||||
Decoration.replace({
|
Decoration.replace({
|
||||||
widget: new Widget(
|
widget: new Widget(
|
||||||
filePath,
|
filePath,
|
||||||
getPreviewByPath,
|
previewByPath,
|
||||||
centered,
|
centered,
|
||||||
figureData
|
figureData
|
||||||
),
|
),
|
||||||
|
@ -910,7 +905,7 @@ export const atomicDecorations = (options: Options) => {
|
||||||
Decoration.replace({
|
Decoration.replace({
|
||||||
widget: new Widget(
|
widget: new Widget(
|
||||||
filePath,
|
filePath,
|
||||||
getPreviewByPath,
|
previewByPath,
|
||||||
centered,
|
centered,
|
||||||
figureData
|
figureData
|
||||||
),
|
),
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { placeSelectionInsideBlock } from '../selection'
|
||||||
import { isEqual } from 'lodash'
|
import { isEqual } from 'lodash'
|
||||||
import { FigureData } from '../../figure-modal'
|
import { FigureData } from '../../figure-modal'
|
||||||
import { debugConsole } from '@/utils/debugging'
|
import { debugConsole } from '@/utils/debugging'
|
||||||
|
import { PreviewPath } from '../../../../../../../types/preview-path'
|
||||||
|
|
||||||
export class GraphicsWidget extends WidgetType {
|
export class GraphicsWidget extends WidgetType {
|
||||||
destroyed = false
|
destroyed = false
|
||||||
|
@ -10,9 +11,7 @@ export class GraphicsWidget extends WidgetType {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public filePath: string,
|
public filePath: string,
|
||||||
public getPreviewByPath: (
|
public previewByPath: (path: string) => PreviewPath | null,
|
||||||
filePath: string
|
|
||||||
) => { url: string; extension: string } | null,
|
|
||||||
public centered: boolean,
|
public centered: boolean,
|
||||||
public figureData: FigureData | null
|
public figureData: FigureData | null
|
||||||
) {
|
) {
|
||||||
|
@ -82,7 +81,7 @@ export class GraphicsWidget extends WidgetType {
|
||||||
renderGraphic(element: HTMLElement, view: EditorView) {
|
renderGraphic(element: HTMLElement, view: EditorView) {
|
||||||
element.textContent = '' // ensure the element is empty
|
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.filepath = this.filePath
|
||||||
element.dataset.width = this.figureData?.width?.toString()
|
element.dataset.width = this.figureData?.width?.toString()
|
||||||
|
|
||||||
|
|
|
@ -24,14 +24,11 @@ import { pasteHtml } from './paste-html'
|
||||||
import { commandTooltip } from '../command-tooltip'
|
import { commandTooltip } from '../command-tooltip'
|
||||||
import { tableGeneratorTheme } from './table-generator'
|
import { tableGeneratorTheme } from './table-generator'
|
||||||
import { debugConsole } from '@/utils/debugging'
|
import { debugConsole } from '@/utils/debugging'
|
||||||
|
import { PreviewPath } from '../../../../../../types/preview-path'
|
||||||
|
|
||||||
type Options = {
|
type Options = {
|
||||||
visual: boolean
|
visual: boolean
|
||||||
fileTreeManager: {
|
previewByPath: (path: string) => PreviewPath | null
|
||||||
getPreviewByPath: (
|
|
||||||
path: string
|
|
||||||
) => { url: string; extension: string } | null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const visualConf = new Compartment()
|
const visualConf = new Compartment()
|
||||||
|
|
|
@ -48,6 +48,7 @@ import { CurrentDoc } from '../../../../../types/current-doc'
|
||||||
import { useErrorHandler } from 'react-error-boundary'
|
import { useErrorHandler } from 'react-error-boundary'
|
||||||
import { setVisual } from '../extensions/visual/visual'
|
import { setVisual } from '../extensions/visual/visual'
|
||||||
import getMeta from '../../../utils/meta'
|
import getMeta from '../../../utils/meta'
|
||||||
|
import { useFileTreePathContext } from '@/features/file-tree/contexts/file-tree-path'
|
||||||
|
|
||||||
function useCodeMirrorScope(view: EditorView) {
|
function useCodeMirrorScope(view: EditorView) {
|
||||||
const ide = useIdeContext()
|
const ide = useIdeContext()
|
||||||
|
@ -244,8 +245,10 @@ function useCodeMirrorScope(view: EditorView) {
|
||||||
|
|
||||||
const editableRef = useRef(permissionsLevel !== 'readOnly')
|
const editableRef = useRef(permissionsLevel !== 'readOnly')
|
||||||
|
|
||||||
|
const { previewByPath } = useFileTreePathContext()
|
||||||
|
|
||||||
const visualRef = useRef({
|
const visualRef = useRef({
|
||||||
fileTreeManager: ide.fileTreeManager,
|
previewByPath,
|
||||||
visual,
|
visual,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -312,6 +315,11 @@ function useCodeMirrorScope(view: EditorView) {
|
||||||
window.dispatchEvent(new Event('editor:visual-switch'))
|
window.dispatchEvent(new Event('editor:visual-switch'))
|
||||||
}, [view, visual])
|
}, [view, visual])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
visualRef.current.previewByPath = previewByPath
|
||||||
|
view.dispatch(setVisual(visualRef.current))
|
||||||
|
}, [view, previewByPath])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
editableRef.current = permissionsLevel !== 'readOnly'
|
editableRef.current = permissionsLevel !== 'readOnly'
|
||||||
view.dispatch(setEditable(editableRef.current)) // the editor needs to be locked when there's a problem saving data
|
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() {
|
closeFile() {
|
||||||
return window.setTimeout(
|
return window.setTimeout(
|
||||||
() => {
|
() => {
|
||||||
|
|
|
@ -29,6 +29,8 @@ import { useEditorContext } from './editor-context'
|
||||||
import { buildFileList } from '../../features/pdf-preview/util/file-list'
|
import { buildFileList } from '../../features/pdf-preview/util/file-list'
|
||||||
import { useLayoutContext } from './layout-context'
|
import { useLayoutContext } from './layout-context'
|
||||||
import { useUserContext } from './user-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()
|
export const LocalCompileContext = createContext()
|
||||||
|
|
||||||
|
@ -95,6 +97,9 @@ export function LocalCompileProvider({ children }) {
|
||||||
|
|
||||||
const { features } = useUserContext()
|
const { features } = useUserContext()
|
||||||
|
|
||||||
|
const { fileTreeData } = useFileTreeData()
|
||||||
|
const { findEntityByPath } = useFileTreePathContext()
|
||||||
|
|
||||||
// whether a compile is in progress
|
// whether a compile is in progress
|
||||||
const [compiling, setCompiling] = useState(false)
|
const [compiling, setCompiling] = useState(false)
|
||||||
|
|
||||||
|
@ -245,6 +250,17 @@ export function LocalCompileProvider({ children }) {
|
||||||
compilingRef.current = compiling
|
compilingRef.current = compiling
|
||||||
}, [compiling])
|
}, [compiling])
|
||||||
|
|
||||||
|
const _buildLogEntryAnnotations = useCallback(
|
||||||
|
entries => buildLogEntryAnnotations(entries, fileTreeData, rootDocId),
|
||||||
|
[fileTreeData, rootDocId]
|
||||||
|
)
|
||||||
|
|
||||||
|
const buildLogEntryAnnotationsRef = useRef(_buildLogEntryAnnotations)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
buildLogEntryAnnotationsRef.current = _buildLogEntryAnnotations
|
||||||
|
}, [_buildLogEntryAnnotations])
|
||||||
|
|
||||||
// the document compiler
|
// the document compiler
|
||||||
const [compiler] = useState(() => {
|
const [compiler] = useState(() => {
|
||||||
return new DocumentCompiler({
|
return new DocumentCompiler({
|
||||||
|
@ -349,10 +365,7 @@ export function LocalCompileProvider({ children }) {
|
||||||
setRawLog(result.log)
|
setRawLog(result.log)
|
||||||
setLogEntries(result.logEntries)
|
setLogEntries(result.logEntries)
|
||||||
setLogEntryAnnotations(
|
setLogEntryAnnotations(
|
||||||
buildLogEntryAnnotations(
|
buildLogEntryAnnotationsRef.current(result.logEntries.all)
|
||||||
result.logEntries.all,
|
|
||||||
ide.fileTreeManager
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// sample compile stats for real users
|
// sample compile stats for real users
|
||||||
|
@ -521,16 +534,16 @@ export function LocalCompileProvider({ children }) {
|
||||||
|
|
||||||
const syncToEntry = useCallback(
|
const syncToEntry = useCallback(
|
||||||
entry => {
|
entry => {
|
||||||
const entity = ide.fileTreeManager.findEntityByPath(entry.file)
|
const result = findEntityByPath(entry.file)
|
||||||
|
|
||||||
if (entity && entity.type === 'doc') {
|
if (result && result.type === 'doc') {
|
||||||
ide.editorManager.openDoc(entity, {
|
ide.editorManager.openDocId(result.entity._id, {
|
||||||
gotoLine: entry.line ?? undefined,
|
gotoLine: entry.line ?? undefined,
|
||||||
gotoColumn: entry.column ?? undefined,
|
gotoColumn: entry.column ?? undefined,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[ide]
|
[findEntityByPath, ide.editorManager]
|
||||||
)
|
)
|
||||||
|
|
||||||
// clear the cache then run a compile, triggered by a menu item
|
// 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 { SplitTestProvider } from './split-test-context'
|
||||||
import { FileTreeDataProvider } from './file-tree-data-context'
|
import { FileTreeDataProvider } from './file-tree-data-context'
|
||||||
import { ProjectSettingsProvider } from '@/features/editor-left-menu/context/project-settings-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 }) {
|
export function ContextRoot({ children, ide }) {
|
||||||
return (
|
return (
|
||||||
|
@ -21,6 +22,7 @@ export function ContextRoot({ children, ide }) {
|
||||||
<UserProvider>
|
<UserProvider>
|
||||||
<ProjectProvider>
|
<ProjectProvider>
|
||||||
<FileTreeDataProvider>
|
<FileTreeDataProvider>
|
||||||
|
<FileTreePathProvider>
|
||||||
<DetachProvider>
|
<DetachProvider>
|
||||||
<EditorProvider>
|
<EditorProvider>
|
||||||
<ProjectSettingsProvider>
|
<ProjectSettingsProvider>
|
||||||
|
@ -34,6 +36,7 @@ export function ContextRoot({ children, ide }) {
|
||||||
</ProjectSettingsProvider>
|
</ProjectSettingsProvider>
|
||||||
</EditorProvider>
|
</EditorProvider>
|
||||||
</DetachProvider>
|
</DetachProvider>
|
||||||
|
</FileTreePathProvider>
|
||||||
</FileTreeDataProvider>
|
</FileTreeDataProvider>
|
||||||
</ProjectProvider>
|
</ProjectProvider>
|
||||||
</UserProvider>
|
</UserProvider>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { useEffect, useMemo } from 'react'
|
import { useEffect, useMemo } from 'react'
|
||||||
import { get } from 'lodash'
|
import { get } from 'lodash'
|
||||||
import { ContextRoot } from '../../js/shared/context/root-context'
|
|
||||||
import { User } from '../../../types/user'
|
import { User } from '../../../types/user'
|
||||||
import { Project } from '../../../types/project'
|
import { Project } from '../../../types/project'
|
||||||
import {
|
import {
|
||||||
|
@ -10,6 +9,18 @@ import {
|
||||||
} from '../fixtures/compile'
|
} from '../fixtures/compile'
|
||||||
import useFetchMock from '../hooks/use-fetch-mock'
|
import useFetchMock from '../hooks/use-fetch-mock'
|
||||||
import { useMeta } from '../hooks/use-meta'
|
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][] = []
|
const scopeWatchers: [string, (value: any) => void][] = []
|
||||||
|
|
||||||
|
@ -97,19 +108,6 @@ const initialize = () => {
|
||||||
on: () => {},
|
on: () => {},
|
||||||
removeListener: () => {},
|
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: {
|
editorManager: {
|
||||||
getCurrentDocId: () => 'foo',
|
getCurrentDocId: () => 'foo',
|
||||||
openDoc: (id: string, options: unknown) => {
|
openDoc: (id: string, options: unknown) => {
|
||||||
|
@ -207,6 +205,7 @@ const initialize = () => {
|
||||||
|
|
||||||
type ScopeDecoratorOptions = {
|
type ScopeDecoratorOptions = {
|
||||||
mockCompileOnLoad: boolean
|
mockCompileOnLoad: boolean
|
||||||
|
providers?: Record<string, any>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ScopeDecorator = (
|
export const ScopeDecorator = (
|
||||||
|
@ -237,9 +236,47 @@ export const ScopeDecorator = (
|
||||||
// set values on window.metaAttributesCache (created in initialize, above)
|
// set values on window.metaAttributesCache (created in initialize, above)
|
||||||
useMeta(meta)
|
useMeta(meta)
|
||||||
|
|
||||||
|
const Providers = {
|
||||||
|
DetachCompileProvider,
|
||||||
|
DetachProvider,
|
||||||
|
EditorProvider,
|
||||||
|
FileTreeDataProvider,
|
||||||
|
FileTreePathProvider,
|
||||||
|
IdeAngularProvider,
|
||||||
|
LayoutProvider,
|
||||||
|
LocalCompileProvider,
|
||||||
|
ProjectProvider,
|
||||||
|
ProjectSettingsProvider,
|
||||||
|
SplitTestProvider,
|
||||||
|
UserProvider,
|
||||||
|
...opts.providers,
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
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 />
|
<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 { ScopeDecorator } from '../decorators/scope'
|
||||||
import { useScope } from '../hooks/use-scope'
|
import { useScope } from '../hooks/use-scope'
|
||||||
import { useMeta } from '../hooks/use-meta'
|
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 {
|
export default {
|
||||||
title: 'Editor / Source Editor',
|
title: 'Editor / Source Editor',
|
||||||
component: SourceEditor,
|
component: SourceEditor,
|
||||||
decorators: [
|
decorators: [
|
||||||
ScopeDecorator,
|
(Story: any) =>
|
||||||
|
ScopeDecorator(Story, {
|
||||||
|
mockCompileOnLoad: true,
|
||||||
|
providers: { FileTreePathProvider },
|
||||||
|
}),
|
||||||
(Story: any) => (
|
(Story: any) => (
|
||||||
<div style={{ height: '90vh' }}>
|
<div style={{ height: '90vh' }}>
|
||||||
<Story />
|
<Story />
|
||||||
|
@ -93,29 +118,6 @@ export const Visual = (args: any, { globals: { theme } }: any) => {
|
||||||
open_doc_name: 'example.tex',
|
open_doc_name: 'example.tex',
|
||||||
showVisual: true,
|
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: {
|
||||||
...settings,
|
...settings,
|
||||||
overallTheme: theme === 'default-' ? '' : theme,
|
overallTheme: theme === 'default-' ? '' : theme,
|
||||||
|
@ -127,6 +129,7 @@ export const Visual = (args: any, { globals: { theme } }: any) => {
|
||||||
'ol-completedTutorials': {
|
'ol-completedTutorials': {
|
||||||
'table-generator-promotion': '2023-09-01T00:00:00.000Z',
|
'table-generator-promotion': '2023-09-01T00:00:00.000Z',
|
||||||
},
|
},
|
||||||
|
'ol-project_id': '63e21c07946dd8c76505f85a',
|
||||||
})
|
})
|
||||||
|
|
||||||
return <SourceEditor />
|
return <SourceEditor />
|
||||||
|
|
|
@ -1,9 +1,31 @@
|
||||||
import { EditorProviders } from '../../helpers/editor-providers'
|
import { EditorProviders } from '../../helpers/editor-providers'
|
||||||
import PdfLogsEntries from '../../../../frontend/js/features/pdf-preview/components/pdf-logs-entries'
|
import PdfLogsEntries from '../../../../frontend/js/features/pdf-preview/components/pdf-logs-entries'
|
||||||
import { detachChannel, testDetachChannel } from '../../helpers/detach-channel'
|
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 () {
|
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 = [
|
const logEntries = [
|
||||||
{
|
{
|
||||||
|
@ -23,11 +45,8 @@ describe('<PdfLogsEntries/>', function () {
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
props = {
|
props = {
|
||||||
fileTreeManager: {
|
|
||||||
findEntityByPath: cy.stub().as('findEntityByPath').returns(fakeEntity),
|
|
||||||
},
|
|
||||||
editorManager: {
|
editorManager: {
|
||||||
openDoc: cy.spy().as('openDoc'),
|
openDocId: cy.spy().as('openDocId'),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +66,7 @@ describe('<PdfLogsEntries/>', function () {
|
||||||
|
|
||||||
it('opens doc on click', function () {
|
it('opens doc on click', function () {
|
||||||
cy.mount(
|
cy.mount(
|
||||||
<EditorProviders {...props}>
|
<EditorProviders {...props} providers={{ FileTreePathProvider }}>
|
||||||
<PdfLogsEntries entries={logEntries} />
|
<PdfLogsEntries entries={logEntries} />
|
||||||
</EditorProviders>
|
</EditorProviders>
|
||||||
)
|
)
|
||||||
|
@ -57,10 +76,14 @@ describe('<PdfLogsEntries/>', function () {
|
||||||
}).click()
|
}).click()
|
||||||
|
|
||||||
cy.get('@findEntityByPath').should('have.been.calledOnce')
|
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,
|
gotoLine: 9,
|
||||||
gotoColumn: 8,
|
gotoColumn: 8,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('opens doc via detached action', function () {
|
it('opens doc via detached action', function () {
|
||||||
|
@ -69,7 +92,7 @@ describe('<PdfLogsEntries/>', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
cy.mount(
|
cy.mount(
|
||||||
<EditorProviders {...props}>
|
<EditorProviders {...props} providers={{ FileTreePathProvider }}>
|
||||||
<PdfLogsEntries entries={logEntries} />
|
<PdfLogsEntries entries={logEntries} />
|
||||||
</EditorProviders>
|
</EditorProviders>
|
||||||
).then(() => {
|
).then(() => {
|
||||||
|
@ -89,10 +112,14 @@ describe('<PdfLogsEntries/>', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
cy.get('@findEntityByPath').should('have.been.calledOnce')
|
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,
|
gotoLine: 7,
|
||||||
gotoColumn: 6,
|
gotoColumn: 6,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('sends open doc clicks via detached action', function () {
|
it('sends open doc clicks via detached action', function () {
|
||||||
|
@ -101,7 +128,7 @@ describe('<PdfLogsEntries/>', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
cy.mount(
|
cy.mount(
|
||||||
<EditorProviders {...props}>
|
<EditorProviders {...props} providers={{ FileTreePathProvider }}>
|
||||||
<PdfLogsEntries entries={logEntries} />
|
<PdfLogsEntries entries={logEntries} />
|
||||||
</EditorProviders>
|
</EditorProviders>
|
||||||
)
|
)
|
||||||
|
@ -113,7 +140,7 @@ describe('<PdfLogsEntries/>', function () {
|
||||||
}).click()
|
}).click()
|
||||||
|
|
||||||
cy.get('@findEntityByPath').should('not.have.been.called')
|
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', {
|
cy.get('@postDetachMessage').should('have.been.calledWith', {
|
||||||
role: 'detached',
|
role: 'detached',
|
||||||
event: 'action-sync-to-entry',
|
event: 'action-sync-to-entry',
|
||||||
|
|
|
@ -1,20 +1,24 @@
|
||||||
import { mockScope } from '../helpers/mock-scope'
|
import { mockScope } from '../helpers/mock-scope'
|
||||||
import { EditorProviders } from '../../../helpers/editor-providers'
|
import { EditorProviders } from '../../../helpers/editor-providers'
|
||||||
import CodemirrorEditor from '../../../../../frontend/js/features/source-editor/components/codemirror-editor'
|
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 }) => (
|
const Container: FC = ({ children }) => (
|
||||||
<div style={{ width: 785, height: 785 }}>{children}</div>
|
<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)
|
const scope = mockScope(content)
|
||||||
scope.permissionsLevel = 'readOnly'
|
scope.permissionsLevel = 'readOnly'
|
||||||
scope.editor.showVisual = true
|
scope.editor.showVisual = true
|
||||||
|
|
||||||
cy.mount(
|
cy.mount(
|
||||||
<Container>
|
<Container>
|
||||||
<EditorProviders scope={scope} {...args}>
|
<EditorProviders scope={scope} {...props}>
|
||||||
<CodemirrorEditor />
|
<CodemirrorEditor />
|
||||||
</EditorProviders>
|
</EditorProviders>
|
||||||
</Container>
|
</Container>
|
||||||
|
@ -81,15 +85,21 @@ describe('<CodeMirrorEditor/> in Visual mode with read-only permission', functio
|
||||||
})
|
})
|
||||||
|
|
||||||
it('does not display the figure edit button', function () {
|
it('does not display the figure edit button', function () {
|
||||||
const fileTreeManager = {
|
const FileTreePathProvider: FC = ({ children }) => (
|
||||||
findEntityById: cy.stub(),
|
<FileTreePathContext.Provider
|
||||||
|
value={{
|
||||||
|
dirname: cy.stub(),
|
||||||
findEntityByPath: cy.stub(),
|
findEntityByPath: cy.stub(),
|
||||||
getEntityPath: cy.stub(),
|
pathInFolder: cy.stub(),
|
||||||
getRootDocDirname: cy.stub(),
|
previewByPath: cy
|
||||||
getPreviewByPath: cy
|
|
||||||
.stub()
|
.stub()
|
||||||
|
.as('previewByPath')
|
||||||
.returns({ url: '/images/frog.jpg', extension: 'jpg' }),
|
.returns({ url: '/images/frog.jpg', extension: 'jpg' }),
|
||||||
}
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</FileTreePathContext.Provider>
|
||||||
|
)
|
||||||
|
|
||||||
cy.intercept('/images/frog.jpg', { fixture: 'images/gradient.png' })
|
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}
|
\\caption{My caption}
|
||||||
\\label{fig:my-label}
|
\\label{fig:my-label}
|
||||||
\\end{figure}`,
|
\\end{figure}`,
|
||||||
{ fileTreeManager }
|
{
|
||||||
|
providers: { FileTreePathProvider },
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
cy.get('img.ol-cm-graphics').should('have.length', 1)
|
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 CodemirrorEditor from '../../../../../frontend/js/features/source-editor/components/codemirror-editor'
|
||||||
import { mockScope } from '../helpers/mock-scope'
|
import { mockScope } from '../helpers/mock-scope'
|
||||||
import forEach from 'mocha-each'
|
import forEach from 'mocha-each'
|
||||||
|
import { FileTreePathContext } from '@/features/file-tree/contexts/file-tree-path'
|
||||||
|
|
||||||
const Container: FC = ({ children }) => (
|
const Container: FC = ({ children }) => (
|
||||||
<div style={{ width: 785, height: 785 }}>{children}</div>
|
<div style={{ width: 785, height: 785 }}>{children}</div>
|
||||||
|
@ -23,9 +24,25 @@ describe('<CodeMirrorEditor/> in Visual mode', function () {
|
||||||
const scope = mockScope(content)
|
const scope = mockScope(content)
|
||||||
scope.editor.showVisual = true
|
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(
|
cy.mount(
|
||||||
<Container>
|
<Container>
|
||||||
<EditorProviders scope={scope}>
|
<EditorProviders scope={scope} providers={{ FileTreePathProvider }}>
|
||||||
<CodemirrorEditor />
|
<CodemirrorEditor />
|
||||||
</EditorProviders>
|
</EditorProviders>
|
||||||
</Container>
|
</Container>
|
||||||
|
|
|
@ -2,6 +2,7 @@ import CodemirrorEditor from '../../../../../frontend/js/features/source-editor/
|
||||||
import { EditorProviders } from '../../../helpers/editor-providers'
|
import { EditorProviders } from '../../../helpers/editor-providers'
|
||||||
import { mockScope, rootFolderId } from '../helpers/mock-scope'
|
import { mockScope, rootFolderId } from '../helpers/mock-scope'
|
||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
|
import { FileTreePathContext } from '@/features/file-tree/contexts/file-tree-path'
|
||||||
|
|
||||||
const Container: FC = ({ children }) => (
|
const Container: FC = ({ children }) => (
|
||||||
<div style={{ width: 1500, height: 785 }}>{children}</div>
|
<div style={{ width: 1500, height: 785 }}>{children}</div>
|
||||||
|
@ -50,9 +51,25 @@ describe('<FigureModal />', function () {
|
||||||
const scope = mockScope(content)
|
const scope = mockScope(content)
|
||||||
scope.editor.showVisual = true
|
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(
|
cy.mount(
|
||||||
<Container>
|
<Container>
|
||||||
<EditorProviders scope={scope}>
|
<EditorProviders scope={scope} providers={{ FileTreePathProvider }}>
|
||||||
<CodemirrorEditor />
|
<CodemirrorEditor />
|
||||||
</EditorProviders>
|
</EditorProviders>
|
||||||
</Container>
|
</Container>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Disable prop type checks for test harnesses
|
// Disable prop type checks for test harnesses
|
||||||
/* eslint-disable react/prop-types */
|
/* eslint-disable react/prop-types */
|
||||||
import sinon from 'sinon'
|
import sinon from 'sinon'
|
||||||
import { get } from 'lodash'
|
import { get, merge } from 'lodash'
|
||||||
import { SplitTestProvider } from '@/shared/context/split-test-context'
|
import { SplitTestProvider } from '@/shared/context/split-test-context'
|
||||||
import { IdeAngularProvider } from '@/shared/context/ide-angular-provider'
|
import { IdeAngularProvider } from '@/shared/context/ide-angular-provider'
|
||||||
import { UserProvider } from '@/shared/context/user-context'
|
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 { LocalCompileProvider } from '@/shared/context/local-compile-context'
|
||||||
import { DetachCompileProvider } from '@/shared/context/detach-compile-context'
|
import { DetachCompileProvider } from '@/shared/context/detach-compile-context'
|
||||||
import { ProjectSettingsProvider } from '@/features/editor-left-menu/context/project-settings-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
|
// these constants can be imported in tests instead of
|
||||||
// using magic strings
|
// using magic strings
|
||||||
|
@ -70,13 +71,15 @@ export function EditorProviders({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
providers = {},
|
||||||
}) {
|
}) {
|
||||||
window.user = user || window.user
|
window.user = user || window.user
|
||||||
window.gitBridgePublicBaseUrl = 'https://git.overleaf.test'
|
window.gitBridgePublicBaseUrl = 'https://git.overleaf.test'
|
||||||
window.project_id = projectId != null ? projectId : window.project_id
|
window.project_id = projectId != null ? projectId : window.project_id
|
||||||
window.isRestrictedTokenMember = isRestrictedTokenMember
|
window.isRestrictedTokenMember = isRestrictedTokenMember
|
||||||
|
|
||||||
const $scope = {
|
const $scope = merge(
|
||||||
|
{
|
||||||
user: window.user,
|
user: window.user,
|
||||||
project: {
|
project: {
|
||||||
_id: window.project_id,
|
_id: window.project_id,
|
||||||
|
@ -95,8 +98,9 @@ export function EditorProviders({
|
||||||
$applyAsync: sinon.stub(),
|
$applyAsync: sinon.stub(),
|
||||||
toggleHistory: sinon.stub(),
|
toggleHistory: sinon.stub(),
|
||||||
permissionsLevel,
|
permissionsLevel,
|
||||||
...scope,
|
},
|
||||||
}
|
scope
|
||||||
|
)
|
||||||
|
|
||||||
window._ide = {
|
window._ide = {
|
||||||
$scope,
|
$scope,
|
||||||
|
@ -109,30 +113,47 @@ export function EditorProviders({
|
||||||
|
|
||||||
// Add details for useUserContext
|
// Add details for useUserContext
|
||||||
window.metaAttributesCache.set('ol-user', { ...user, features })
|
window.metaAttributesCache.set('ol-user', { ...user, features })
|
||||||
|
const Providers = {
|
||||||
|
DetachCompileProvider,
|
||||||
|
DetachProvider,
|
||||||
|
EditorProvider,
|
||||||
|
FileTreeDataProvider,
|
||||||
|
FileTreePathProvider,
|
||||||
|
IdeAngularProvider,
|
||||||
|
LayoutProvider,
|
||||||
|
LocalCompileProvider,
|
||||||
|
ProjectProvider,
|
||||||
|
ProjectSettingsProvider,
|
||||||
|
SplitTestProvider,
|
||||||
|
UserProvider,
|
||||||
|
...providers,
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SplitTestProvider>
|
<Providers.SplitTestProvider>
|
||||||
<IdeAngularProvider ide={window._ide}>
|
<Providers.IdeAngularProvider ide={window._ide}>
|
||||||
<UserProvider>
|
<Providers.UserProvider>
|
||||||
<ProjectProvider>
|
<Providers.ProjectProvider>
|
||||||
<FileTreeDataProvider>
|
<Providers.FileTreeDataProvider>
|
||||||
<DetachProvider>
|
<Providers.FileTreePathProvider>
|
||||||
<EditorProvider>
|
<Providers.DetachProvider>
|
||||||
<ProjectSettingsProvider>
|
<Providers.EditorProvider>
|
||||||
<LayoutProvider>
|
<Providers.ProjectSettingsProvider>
|
||||||
<LocalCompileProvider>
|
<Providers.LayoutProvider>
|
||||||
<DetachCompileProvider>
|
<Providers.LocalCompileProvider>
|
||||||
|
<Providers.DetachCompileProvider>
|
||||||
{children}
|
{children}
|
||||||
</DetachCompileProvider>
|
</Providers.DetachCompileProvider>
|
||||||
</LocalCompileProvider>
|
</Providers.LocalCompileProvider>
|
||||||
</LayoutProvider>
|
</Providers.LayoutProvider>
|
||||||
</ProjectSettingsProvider>
|
</Providers.ProjectSettingsProvider>
|
||||||
</EditorProvider>
|
</Providers.EditorProvider>
|
||||||
</DetachProvider>
|
</Providers.DetachProvider>
|
||||||
</FileTreeDataProvider>
|
</Providers.FileTreePathProvider>
|
||||||
</ProjectProvider>
|
</Providers.FileTreeDataProvider>
|
||||||
</UserProvider>
|
</Providers.ProjectProvider>
|
||||||
</IdeAngularProvider>
|
</Providers.UserProvider>
|
||||||
</SplitTestProvider>
|
</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