overleaf/services/web/frontend/js/features/preview/components/preview-toolbar.js

292 lines
9.3 KiB
JavaScript
Raw Normal View History

import React, { useRef, useState } from 'react'
import PropTypes from 'prop-types'
import { OverlayTrigger, Tooltip } from 'react-bootstrap'
import { useTranslation } from 'react-i18next'
import PreviewDownloadButton from './preview-download-button'
import PreviewRecompileButton from './preview-recompile-button'
import PreviewLogsToggleButton from './preview-logs-toggle-button'
import useResizeObserver from '../../../shared/hooks/use-resize-observer'
import Icon from '../../../shared/components/icon'
function _getElementWidth(element) {
if (!element) return 0
return Math.ceil(element.getBoundingClientRect().width)
}
function PreviewToolbar({
compilerState,
logsState,
onRecompileFromScratch,
onRecompile,
onRunSyntaxCheckNow,
onSetAutoCompile,
onSetDraftMode,
onSetSyntaxCheck,
onToggleLogs,
onSetSplitLayout,
onSetFullLayout,
onStopCompilation,
outputFiles,
pdfDownloadUrl,
showLogs,
splitLayout,
}) {
const showTextRef = useRef(true)
const showToggleTextRef = useRef(true)
const toolbarRef = useRef()
const recompileWidthDifferenceRef = useRef()
const recompileLongerTextRef = useRef()
const [showText, setShowText] = useState(showTextRef.current)
const [showToggleText, setShowToggleText] = useState(
showToggleTextRef.current
)
const { t } = useTranslation()
function checkCanShowText(observedElement) {
// toolbar items can be in 3 states:
// all text, only toggle logs w/text and icons on others, all icons
// states depend on available space in the toolbar
const toolbarWidth =
observedElement &&
observedElement.contentRect &&
observedElement.contentRect.width
if (!toolbarWidth) return
_checkRecompileStateWidths()
let textWidths = 0
let itemsWidth = _getItemsWidth() // could be with or without text
// get widths of text only
// required for _checkToggleText, even if currently showing text
const textElements = toolbarRef.current.querySelectorAll('.toolbar-text')
textElements.forEach(item => {
if (item.getAttribute('aria-hidden') !== 'true') {
textWidths += _getElementWidth(item)
}
})
const logsToggleText = toolbarRef.current.querySelector(
'#logs-toggle .toolbar-text'
)
const logsToggleTextWidth = _getElementWidth(logsToggleText)
if (!showTextRef.current && !showToggleTextRef.current) {
// itemsWidth was calculated without any text shown
itemsWidth += textWidths
} else if (!showTextRef.current && showToggleTextRef.current) {
// itemsWidth was calculated with toggle text but no other text
// only add text width for other items and then
// subtract toggle text width, since it is already in itemsWidth
itemsWidth += parseInt(textWidths - logsToggleTextWidth, 10)
}
// only add extra if recompile button is in state with smaller length
if (
recompileWidthDifferenceRef.current &&
recompileLongerTextRef.current &&
((!compilerState.isCompiling &&
recompileLongerTextRef.current === 'compiling') ||
(compilerState.isCompiling &&
recompileLongerTextRef.current === 'recompile'))
) {
itemsWidth += recompileWidthDifferenceRef.current
}
itemsWidth += 10 // add extra for some spacing between items
const canShowText = itemsWidth < toolbarWidth
if (!canShowText) {
_checkToggleText(
toolbarWidth,
logsToggleTextWidth,
itemsWidth,
textWidths
)
} else if (showToggleTextRef.current !== true) {
setShowToggleText(true)
showToggleTextRef.current = true
}
setShowText(canShowText)
showTextRef.current = canShowText
}
function _checkRecompileStateWidths() {
// check recompile/compiling button text.
// Do not want to hide and then show text when recompiling
if (
recompileWidthDifferenceRef.current ||
recompileWidthDifferenceRef.current === 0
)
return
const textCompiling = toolbarRef.current.querySelector('#text-compiling')
const textRecompile = toolbarRef.current.querySelector('#text-recompile')
const textCompilingWidth = _getElementWidth(textCompiling)
const textRecompileWidth = _getElementWidth(textRecompile)
const textLengthDifference = Math.abs(
parseInt(textCompilingWidth - textRecompileWidth, 10)
)
recompileWidthDifferenceRef.current = textLengthDifference
// ignore if equal
if (textRecompileWidth > textCompilingWidth) {
recompileLongerTextRef.current = 'recompile'
} else if (textRecompileWidth < textCompilingWidth) {
recompileLongerTextRef.current = 'compiling'
}
}
function _checkToggleText(
toolbarWidth,
logsToggleTextWidth,
itemsWithTextWidth,
textWidths
) {
// check to see if we can still show the toggle button text
let toggleWithTextWidth = 0
let toggleWithoutTextWidth = 0
const itemsWithoutTextWidth = parseInt(itemsWithTextWidth - textWidths, 10)
const logsToggle = toolbarRef.current.querySelector('#logs-toggle')
const logsToggleWidth = _getElementWidth(logsToggle)
// logsToggleWidth could be with or without text
if (showToggleTextRef.current) {
toggleWithTextWidth = logsToggleWidth
toggleWithoutTextWidth = parseInt(
logsToggleWidth - logsToggleTextWidth,
10
)
} else {
toggleWithTextWidth = parseInt(logsToggleWidth + logsToggleTextWidth, 10)
toggleWithoutTextWidth = logsToggleWidth
}
const itemsWithoutTextAndToggleWidth = parseInt(
itemsWithoutTextWidth - toggleWithoutTextWidth,
10
)
const itemsWithIconsExceptToggleWidth = parseInt(
itemsWithoutTextAndToggleWidth + toggleWithTextWidth,
10
)
const canShowToggleText = itemsWithIconsExceptToggleWidth < toolbarWidth
if (canShowToggleText !== showToggleTextRef.current) {
setShowToggleText(canShowToggleText)
showToggleTextRef.current = canShowToggleText
}
}
function _getItemsWidth() {
const toolbarItems = toolbarRef.current.querySelectorAll('.toolbar-item')
let itemWidth = 0
toolbarItems.forEach(item => {
itemWidth += _getElementWidth(item)
})
return itemWidth
}
const pdfExpandLabel = splitLayout ? t('full_screen') : t('split_screen')
const pdfExpandIconType = splitLayout ? 'expand' : 'compress'
const pdfExpandTooltip = (
<Tooltip id="expand-pdf-btn">{pdfExpandLabel}</Tooltip>
)
function handlePdfExpandBtnClick() {
if (splitLayout) {
onSetFullLayout()
} else {
onSetSplitLayout()
}
}
useResizeObserver(toolbarRef, logsState, checkCanShowText)
return (
<div
className="toolbar toolbar-pdf"
id="toolbar-preview"
data-testid="toolbar-preview"
ref={toolbarRef}
>
<div className="toolbar-pdf-left">
<PreviewRecompileButton
compilerState={compilerState}
onRecompile={onRecompile}
onRecompileFromScratch={onRecompileFromScratch}
onRunSyntaxCheckNow={onRunSyntaxCheckNow}
onSetAutoCompile={onSetAutoCompile}
onSetDraftMode={onSetDraftMode}
onSetSyntaxCheck={onSetSyntaxCheck}
onStopCompilation={onStopCompilation}
showText={showText}
/>
<PreviewDownloadButton
isCompiling={compilerState.isCompiling}
outputFiles={outputFiles}
pdfDownloadUrl={pdfDownloadUrl}
showText={showText}
/>
</div>
<div className="toolbar-pdf-right">
<PreviewLogsToggleButton
logsState={logsState}
showLogs={showLogs}
autoCompileLintingError={
compilerState.isAutoCompileOn &&
compilerState.autoCompileHasLintingError
}
compileFailed={compilerState.compileFailed}
onToggle={onToggleLogs}
showText={showToggleText}
/>
<OverlayTrigger placement="left" overlay={pdfExpandTooltip}>
<button
onClick={handlePdfExpandBtnClick}
className="toolbar-pdf-expand-btn toolbar-item"
aria-label={pdfExpandLabel}
>
<Icon type={pdfExpandIconType} />
</button>
</OverlayTrigger>
</div>
</div>
)
}
PreviewToolbar.propTypes = {
compilerState: PropTypes.shape({
autoCompileHasLintingError: PropTypes.bool,
isAutoCompileOn: PropTypes.bool.isRequired,
isCompiling: PropTypes.bool.isRequired,
isDraftModeOn: PropTypes.bool.isRequired,
isSyntaxCheckOn: PropTypes.bool.isRequired,
compileFailed: PropTypes.bool,
logEntries: PropTypes.object.isRequired,
}),
logsState: PropTypes.shape({
nErrors: PropTypes.number.isRequired,
nWarnings: PropTypes.number.isRequired,
}),
showLogs: PropTypes.bool.isRequired,
splitLayout: PropTypes.bool.isRequired,
onRecompile: PropTypes.func.isRequired,
onRecompileFromScratch: PropTypes.func.isRequired,
onRunSyntaxCheckNow: PropTypes.func.isRequired,
onSetAutoCompile: PropTypes.func.isRequired,
onSetDraftMode: PropTypes.func.isRequired,
onSetSyntaxCheck: PropTypes.func.isRequired,
onToggleLogs: PropTypes.func.isRequired,
onSetSplitLayout: PropTypes.func.isRequired,
onSetFullLayout: PropTypes.func.isRequired,
onStopCompilation: PropTypes.func.isRequired,
pdfDownloadUrl: PropTypes.string,
outputFiles: PropTypes.array,
}
export default PreviewToolbar