2023-09-27 13:01:30 -04:00
|
|
|
import { useState, useCallback, type ElementType } from 'react'
|
2021-04-28 07:41:20 -04:00
|
|
|
import PropTypes from 'prop-types'
|
2021-05-21 07:32:51 -04:00
|
|
|
import { Trans, useTranslation } from 'react-i18next'
|
|
|
|
|
2021-04-28 07:41:20 -04:00
|
|
|
import Icon from '../../../shared/components/icon'
|
|
|
|
import { formatTime, relativeDate } from '../../utils/format-date'
|
|
|
|
import { postJSON } from '../../../infrastructure/fetch-json'
|
2021-09-29 05:05:12 -04:00
|
|
|
import { useEditorContext } from '../../../shared/context/editor-context'
|
2021-06-25 04:13:17 -04:00
|
|
|
import { useProjectContext } from '../../../shared/context/project-context'
|
2021-04-28 07:41:20 -04:00
|
|
|
|
2021-05-21 07:32:51 -04:00
|
|
|
import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
|
2021-06-23 04:09:23 -04:00
|
|
|
import useAbortController from '../../../shared/hooks/use-abort-controller'
|
2022-01-19 06:56:57 -05:00
|
|
|
import { LinkedFileIcon } from './file-view-icons'
|
2023-09-27 13:01:30 -04:00
|
|
|
import { BinaryFile, hasProvider, LinkedFile } from '../types/binary-file'
|
2023-09-27 05:45:49 -04:00
|
|
|
import { debugConsole } from '@/utils/debugging'
|
2023-09-27 13:01:30 -04:00
|
|
|
|
|
|
|
const tprLinkedFileInfo = importOverleafModules('tprLinkedFileInfo') as {
|
|
|
|
import: { LinkedFileInfo: ElementType }
|
|
|
|
path: string
|
|
|
|
}[]
|
|
|
|
|
2021-04-28 07:41:20 -04:00
|
|
|
const tprLinkedFileRefreshError = importOverleafModules(
|
|
|
|
'tprLinkedFileRefreshError'
|
2023-09-27 13:01:30 -04:00
|
|
|
) as {
|
|
|
|
import: { LinkedFileRefreshError: ElementType }
|
|
|
|
path: string
|
|
|
|
}[]
|
2021-04-28 07:41:20 -04:00
|
|
|
|
|
|
|
const MAX_URL_LENGTH = 60
|
|
|
|
const FRONT_OF_URL_LENGTH = 35
|
|
|
|
const FILLER = '...'
|
|
|
|
const TAIL_OF_URL_LENGTH = MAX_URL_LENGTH - FRONT_OF_URL_LENGTH - FILLER.length
|
|
|
|
|
2023-09-27 13:01:30 -04:00
|
|
|
function shortenedUrl(url: string) {
|
2021-04-28 07:41:20 -04:00
|
|
|
if (!url) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if (url.length > MAX_URL_LENGTH) {
|
|
|
|
const front = url.slice(0, FRONT_OF_URL_LENGTH)
|
|
|
|
const tail = url.slice(url.length - TAIL_OF_URL_LENGTH)
|
|
|
|
return front + FILLER + tail
|
|
|
|
}
|
|
|
|
return url
|
|
|
|
}
|
|
|
|
|
2023-09-27 13:01:30 -04:00
|
|
|
type FileViewHeaderProps = {
|
|
|
|
file: BinaryFile
|
|
|
|
storeReferencesKeys: (keys: string[]) => void
|
|
|
|
}
|
|
|
|
|
|
|
|
export default function FileViewHeader({
|
|
|
|
file,
|
|
|
|
storeReferencesKeys,
|
|
|
|
}: FileViewHeaderProps) {
|
2021-06-25 04:13:17 -04:00
|
|
|
const { _id: projectId } = useProjectContext({
|
|
|
|
_id: PropTypes.string.isRequired,
|
2021-05-21 07:32:51 -04:00
|
|
|
})
|
2021-09-29 05:05:12 -04:00
|
|
|
const { permissionsLevel } = useEditorContext({
|
2021-10-08 05:25:13 -04:00
|
|
|
permissionsLevel: PropTypes.string,
|
2021-09-29 05:05:12 -04:00
|
|
|
})
|
2021-04-28 07:41:20 -04:00
|
|
|
const { t } = useTranslation()
|
|
|
|
|
2021-05-21 07:32:51 -04:00
|
|
|
const [refreshing, setRefreshing] = useState(false)
|
|
|
|
const [refreshError, setRefreshError] = useState(null)
|
|
|
|
|
2021-06-23 04:09:23 -04:00
|
|
|
const { signal } = useAbortController()
|
|
|
|
|
2021-04-28 07:41:20 -04:00
|
|
|
let fileInfo
|
|
|
|
if (file.linkedFileData) {
|
2023-09-27 13:01:30 -04:00
|
|
|
if (hasProvider(file, 'url')) {
|
2021-04-28 07:41:20 -04:00
|
|
|
fileInfo = (
|
|
|
|
<div>
|
|
|
|
<UrlProvider file={file} />
|
|
|
|
</div>
|
|
|
|
)
|
2023-09-27 13:01:30 -04:00
|
|
|
} else if (hasProvider(file, 'project_file')) {
|
2021-04-28 07:41:20 -04:00
|
|
|
fileInfo = (
|
|
|
|
<div>
|
|
|
|
<ProjectFilePathProvider file={file} />
|
|
|
|
</div>
|
|
|
|
)
|
2023-09-27 13:01:30 -04:00
|
|
|
} else if (hasProvider(file, 'project_output_file')) {
|
2021-04-28 07:41:20 -04:00
|
|
|
fileInfo = (
|
|
|
|
<div>
|
|
|
|
<ProjectOutputFileProvider file={file} />
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const refreshFile = useCallback(() => {
|
|
|
|
setRefreshing(true)
|
|
|
|
// Replacement of the file handled by the file tree
|
|
|
|
window.expectingLinkedFileRefreshedSocketFor = file.name
|
2021-06-23 04:09:23 -04:00
|
|
|
postJSON(`/project/${projectId}/linked_file/${file.id}/refresh`, { signal })
|
2021-04-28 07:41:20 -04:00
|
|
|
.then(() => {
|
2021-06-23 04:09:23 -04:00
|
|
|
setRefreshing(false)
|
2021-04-28 07:41:20 -04:00
|
|
|
})
|
|
|
|
.catch(err => {
|
2021-06-23 04:09:23 -04:00
|
|
|
setRefreshing(false)
|
2021-07-30 04:57:48 -04:00
|
|
|
setRefreshError(err.data?.message || err.message)
|
2021-04-28 07:41:20 -04:00
|
|
|
})
|
|
|
|
.finally(() => {
|
|
|
|
if (
|
2023-09-27 13:01:30 -04:00
|
|
|
hasProvider(file, 'mendeley') ||
|
|
|
|
hasProvider(file, 'zotero') ||
|
2021-04-28 07:41:20 -04:00
|
|
|
file.name.match(/^.*\.bib$/)
|
|
|
|
) {
|
|
|
|
reindexReferences()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
function reindexReferences() {
|
|
|
|
const opts = {
|
|
|
|
body: { shouldBroadcast: true },
|
|
|
|
}
|
|
|
|
|
2021-05-21 07:32:51 -04:00
|
|
|
postJSON(`/project/${projectId}/references/indexAll`, opts)
|
2021-04-28 07:41:20 -04:00
|
|
|
.then(response => {
|
|
|
|
// Later updated by the socket but also updated here for immediate use
|
|
|
|
storeReferencesKeys(response.keys)
|
|
|
|
})
|
2023-09-27 05:45:49 -04:00
|
|
|
.catch(debugConsole.error)
|
2021-04-28 07:41:20 -04:00
|
|
|
}
|
2021-06-23 04:09:23 -04:00
|
|
|
}, [file, projectId, signal, storeReferencesKeys])
|
2021-04-28 07:41:20 -04:00
|
|
|
|
|
|
|
return (
|
2021-06-10 07:26:04 -04:00
|
|
|
<div>
|
2021-04-28 07:41:20 -04:00
|
|
|
{file.linkedFileData && fileInfo}
|
|
|
|
{file.linkedFileData &&
|
|
|
|
tprLinkedFileInfo.map(({ import: { LinkedFileInfo }, path }) => (
|
|
|
|
<LinkedFileInfo key={path} file={file} />
|
|
|
|
))}
|
2021-09-29 05:05:12 -04:00
|
|
|
{file.linkedFileData && permissionsLevel !== 'readOnly' && (
|
2021-04-28 07:41:20 -04:00
|
|
|
<button
|
2023-07-12 11:52:40 -04:00
|
|
|
className="btn btn-primary"
|
2021-04-28 07:41:20 -04:00
|
|
|
onClick={refreshFile}
|
2023-07-12 11:52:40 -04:00
|
|
|
disabled={refreshing}
|
2021-04-28 07:41:20 -04:00
|
|
|
>
|
2022-01-19 06:56:57 -05:00
|
|
|
<Icon type="refresh" spin={refreshing} fw />
|
2021-04-28 07:41:20 -04:00
|
|
|
<span>{refreshing ? t('refreshing') + '...' : t('refresh')}</span>
|
|
|
|
</button>
|
|
|
|
)}
|
|
|
|
|
|
|
|
<a
|
2023-03-08 09:42:51 -05:00
|
|
|
download
|
2021-05-21 07:32:51 -04:00
|
|
|
href={`/project/${projectId}/file/${file.id}`}
|
2023-01-11 11:28:23 -05:00
|
|
|
className="btn btn-secondary-info btn-secondary"
|
2021-04-28 07:41:20 -04:00
|
|
|
>
|
2022-01-19 06:56:57 -05:00
|
|
|
<Icon type="download" fw />
|
2021-05-07 09:20:55 -04:00
|
|
|
|
|
|
|
<span>{t('download')}</span>
|
2021-04-28 07:41:20 -04:00
|
|
|
</a>
|
|
|
|
{refreshError && (
|
|
|
|
<div className="row">
|
|
|
|
<br />
|
2023-07-12 11:52:40 -04:00
|
|
|
<div className="alert alert-danger col-md-6 col-md-offset-3">
|
|
|
|
{t('access_denied')}: {refreshError}
|
|
|
|
{tprLinkedFileRefreshError.map(
|
|
|
|
({ import: { LinkedFileRefreshError }, path }) => (
|
|
|
|
<LinkedFileRefreshError key={path} file={file} />
|
|
|
|
)
|
|
|
|
)}
|
2021-04-28 07:41:20 -04:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2023-09-27 13:01:30 -04:00
|
|
|
type UrlProviderProps = {
|
|
|
|
file: LinkedFile<'url'>
|
2021-04-28 07:41:20 -04:00
|
|
|
}
|
|
|
|
|
2023-09-27 13:01:30 -04:00
|
|
|
function UrlProvider({ file }: UrlProviderProps) {
|
2021-04-28 07:41:20 -04:00
|
|
|
return (
|
|
|
|
<p>
|
2022-01-19 06:56:57 -05:00
|
|
|
<LinkedFileIcon />
|
2021-04-28 07:41:20 -04:00
|
|
|
|
|
|
|
<Trans
|
|
|
|
i18nKey="imported_from_external_provider_at_date"
|
|
|
|
components={
|
|
|
|
/* eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-key */
|
|
|
|
[<a href={file.linkedFileData.url} />]
|
|
|
|
}
|
|
|
|
values={{
|
|
|
|
shortenedUrl: shortenedUrl(file.linkedFileData.url),
|
|
|
|
formattedDate: formatTime(file.created),
|
|
|
|
relativeDate: relativeDate(file.created),
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
</p>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2023-09-27 13:01:30 -04:00
|
|
|
type ProjectFilePathProviderProps = {
|
|
|
|
file: LinkedFile<'project_file'>
|
2021-04-28 07:41:20 -04:00
|
|
|
}
|
|
|
|
|
2023-09-27 13:01:30 -04:00
|
|
|
function ProjectFilePathProvider({ file }: ProjectFilePathProviderProps) {
|
2021-04-28 07:41:20 -04:00
|
|
|
/* eslint-disable jsx-a11y/anchor-has-content, react/jsx-key */
|
|
|
|
return (
|
|
|
|
<p>
|
2022-01-19 06:56:57 -05:00
|
|
|
<LinkedFileIcon />
|
2021-04-28 07:41:20 -04:00
|
|
|
|
|
|
|
<Trans
|
|
|
|
i18nKey="imported_from_another_project_at_date"
|
|
|
|
components={
|
|
|
|
file.linkedFileData.v1_source_doc_id
|
|
|
|
? [<span />]
|
|
|
|
: [
|
|
|
|
<a
|
|
|
|
href={`/project/${file.linkedFileData.source_project_id}`}
|
|
|
|
target="_blank"
|
2021-09-14 06:43:57 -04:00
|
|
|
rel="noopener"
|
2021-04-28 07:41:20 -04:00
|
|
|
/>,
|
|
|
|
]
|
|
|
|
}
|
|
|
|
values={{
|
|
|
|
sourceEntityPath: file.linkedFileData.source_entity_path.slice(1),
|
|
|
|
formattedDate: formatTime(file.created),
|
|
|
|
relativeDate: relativeDate(file.created),
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
</p>
|
|
|
|
/* esline-enable jsx-a11y/anchor-has-content, react/jsx-key */
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2023-09-27 13:01:30 -04:00
|
|
|
type ProjectOutputFileProviderProps = {
|
|
|
|
file: LinkedFile<'project_output_file'>
|
2021-04-28 07:41:20 -04:00
|
|
|
}
|
|
|
|
|
2023-09-27 13:01:30 -04:00
|
|
|
function ProjectOutputFileProvider({ file }: ProjectOutputFileProviderProps) {
|
2021-04-28 07:41:20 -04:00
|
|
|
return (
|
|
|
|
<p>
|
2022-01-19 06:56:57 -05:00
|
|
|
<LinkedFileIcon />
|
2021-04-28 07:41:20 -04:00
|
|
|
|
|
|
|
<Trans
|
|
|
|
i18nKey="imported_from_the_output_of_another_project_at_date"
|
|
|
|
components={
|
|
|
|
file.linkedFileData.v1_source_doc_id
|
|
|
|
? [<span />]
|
|
|
|
: [
|
|
|
|
<a
|
|
|
|
href={`/project/${file.linkedFileData.source_project_id}`}
|
|
|
|
target="_blank"
|
2021-09-14 06:43:57 -04:00
|
|
|
rel="noopener"
|
2021-04-28 07:41:20 -04:00
|
|
|
/>,
|
|
|
|
]
|
|
|
|
}
|
|
|
|
values={{
|
|
|
|
sourceOutputFilePath: file.linkedFileData.source_output_file_path,
|
|
|
|
formattedDate: formatTime(file.created),
|
|
|
|
relativeDate: relativeDate(file.created),
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
</p>
|
|
|
|
)
|
|
|
|
}
|