diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index 45ec81982a..d3f65883fb 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -55,8 +55,8 @@ "conflicting_paths_found": "", "connected_users": "", "contact_message_label": "", - "continue_github_merge": "", "contact_us": "", + "continue_github_merge": "", "copy": "", "copy_project": "", "copying": "", @@ -179,9 +179,10 @@ "log_entry_maximum_entries_message": "", "log_entry_maximum_entries_title": "", "log_hint_extra_info": "", + "log_viewer_error": "", + "logs_and_output_files": "", "logs_pane_info_message": "", "logs_pane_info_message_popup": "", - "log_viewer_error": "", "main_file_not_found": "", "make_private": "", "manage_files_from_your_dropbox_folder": "", @@ -257,7 +258,7 @@ "project_too_large": "", "project_too_large_please_reduce": "", "project_too_much_editable_text": "", - "project_url" : "", + "project_url": "", "public": "", "pull_github_changes_into_sharelatex": "", "push_sharelatex_changes_to_github": "", @@ -269,6 +270,7 @@ "recent_commits_in_github": "", "recompile": "", "recompile_from_scratch": "", + "recompile_pdf": "", "reconnect": "", "reference_error_relink_hint": "", "refresh": "", diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-compile-button.js b/services/web/frontend/js/features/pdf-preview/components/pdf-compile-button.js index e463bcc8cb..4b4694ab71 100644 --- a/services/web/frontend/js/features/pdf-preview/components/pdf-compile-button.js +++ b/services/web/frontend/js/features/pdf-preview/components/pdf-compile-button.js @@ -1,4 +1,10 @@ -import { Button, Dropdown, MenuItem } from 'react-bootstrap' +import { + Button, + Dropdown, + MenuItem, + OverlayTrigger, + Tooltip, +} from 'react-bootstrap' import Icon from '../../../shared/components/icon' import ControlledDropdown from '../../../shared/components/controlled-dropdown' import { useTranslation } from 'react-i18next' @@ -6,6 +12,8 @@ import { usePdfPreviewContext } from '../contexts/pdf-preview-context' import { memo } from 'react' import classnames from 'classnames' +const modifierKey = /Mac/i.test(navigator.platform) ? 'Cmd' : 'Ctrl' + function PdfCompileButton() { const { autoCompile, @@ -34,17 +42,28 @@ function PdfCompileButton() { })} id="pdf-recompile-dropdown" > - + + { + setShowLogs(value => { + if (!value) { + sendMBOnce('ide-open-logs-once') + } + + return !value + }) + }, [setShowLogs]) + + if (!codeCheckFailed) { + return null + } + + return ( + + ) +} + +export default memo(PdfHybridCodeCheckButton) diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-hybrid-download-button.js b/services/web/frontend/js/features/pdf-preview/components/pdf-hybrid-download-button.js new file mode 100644 index 0000000000..5567adf427 --- /dev/null +++ b/services/web/frontend/js/features/pdf-preview/components/pdf-hybrid-download-button.js @@ -0,0 +1,36 @@ +import { useTranslation } from 'react-i18next' +import { Button, OverlayTrigger, Tooltip } from 'react-bootstrap' +import Icon from '../../../shared/components/icon' +import { usePdfPreviewContext } from '../contexts/pdf-preview-context' +import { memo } from 'react' + +function PdfHybridDownloadButton() { + const { pdfDownloadUrl } = usePdfPreviewContext() + + const { t } = useTranslation() + + return ( + + {pdfDownloadUrl + ? t('download_pdf') + : t('please_compile_pdf_before_download')} + + } + > + + + ) +} + +export default memo(PdfHybridDownloadButton) diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-hybrid-logs-button.js b/services/web/frontend/js/features/pdf-preview/components/pdf-hybrid-logs-button.js new file mode 100644 index 0000000000..5c414c826d --- /dev/null +++ b/services/web/frontend/js/features/pdf-preview/components/pdf-hybrid-logs-button.js @@ -0,0 +1,55 @@ +import { memo, useCallback } from 'react' +import { useTranslation } from 'react-i18next' +import { Button, Label, OverlayTrigger, Tooltip } from 'react-bootstrap' +import { usePdfPreviewContext } from '../contexts/pdf-preview-context' +import { sendMBOnce } from '../../../infrastructure/event-tracking' +import Icon from '../../../shared/components/icon' + +function PdfHybridLogsButton() { + const { error, logEntries, setShowLogs, showLogs } = usePdfPreviewContext() + + const { t } = useTranslation() + + const handleClick = useCallback(() => { + setShowLogs(value => { + if (!value) { + sendMBOnce('ide-open-logs-once') + } + + return !value + }) + }, [setShowLogs]) + + const errorCount = Number(logEntries?.errors?.length) + const warningCount = Number(logEntries?.warnings?.length) + const totalCount = errorCount + warningCount + + return ( + {t('logs_and_output_files')} + } + > + + + ) +} + +export default memo(PdfHybridLogsButton) diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-logs-viewer.js b/services/web/frontend/js/features/pdf-preview/components/pdf-logs-viewer.js index 6f6ee71a8d..54416d1552 100644 --- a/services/web/frontend/js/features/pdf-preview/components/pdf-logs-viewer.js +++ b/services/web/frontend/js/features/pdf-preview/components/pdf-logs-viewer.js @@ -1,4 +1,3 @@ -import Icon from '../../../shared/components/icon' import { useTranslation } from 'react-i18next' import PreviewLogsPaneEntry from '../../preview/components/preview-logs-pane-entry' import { usePdfPreviewContext } from '../contexts/pdf-preview-context' @@ -11,7 +10,8 @@ import PdfDownloadFilesButton from './pdf-download-files-button' import PdfLogsEntries from './pdf-logs-entries' import withErrorBoundary from '../../../infrastructure/error-boundary' import ErrorBoundaryFallback from './error-boundary-fallback' -import { LogsPaneInfoNotice } from '../../preview/components/preview-logs-pane' +import PdfCodeCheckFailedNotice from '../../preview/components/pdf-code-check-failed-notice' +import PdfLogsPaneInfoNotice from '../../preview/components/pdf-logs-pane-info-notice' function PdfLogsViewer() { const { @@ -27,19 +27,9 @@ function PdfLogsViewer() { return (
- - {codeCheckFailed && ( -
-
-
- -
-

- {t('code_check_failed_explanation')} -

-
-
- )} + + + {codeCheckFailed && } {error && } diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-preview-hybrid-toolbar.js b/services/web/frontend/js/features/pdf-preview/components/pdf-preview-hybrid-toolbar.js new file mode 100644 index 0000000000..f9ded9f3e4 --- /dev/null +++ b/services/web/frontend/js/features/pdf-preview/components/pdf-preview-hybrid-toolbar.js @@ -0,0 +1,25 @@ +import { memo } from 'react' +import { ButtonToolbar } from 'react-bootstrap' +import PdfCompileButton from './pdf-compile-button' +import PdfExpandButton from './pdf-expand-button' +import PdfHybridLogsButton from './pdf-hybrid-logs-button' +import PdfHybridDownloadButton from './pdf-hybrid-download-button' +import PdfHybridCodeCheckButton from './pdf-hybrid-code-check-button' + +function PdfPreviewHybridToolbar() { + return ( + +
+ + + +
+
+ + +
+
+ ) +} + +export default memo(PdfPreviewHybridToolbar) diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-preview-pane.js b/services/web/frontend/js/features/pdf-preview/components/pdf-preview-pane.js index 456a13dcfe..7317a364f6 100644 --- a/services/web/frontend/js/features/pdf-preview/components/pdf-preview-pane.js +++ b/services/web/frontend/js/features/pdf-preview/components/pdf-preview-pane.js @@ -2,15 +2,20 @@ import { memo, Suspense } from 'react' import PdfLogsViewer from './pdf-logs-viewer' import PdfViewer from './pdf-viewer' import { usePdfPreviewContext } from '../contexts/pdf-preview-context' -import PdfPreviewToolbar from './pdf-preview-toolbar' import LoadingSpinner from '../../../shared/components/loading-spinner' +import PdfHybridPreviewToolbar from './pdf-preview-hybrid-toolbar' +import PdfPreviewToolbar from './pdf-preview-toolbar' + +const newPreviewToolbar = new URLSearchParams(window.location.search).has( + 'new_preview_toolbar' +) function PdfPreviewPane() { const { showLogs } = usePdfPreviewContext() return (
- + {newPreviewToolbar ? : } }>
diff --git a/services/web/frontend/js/features/preview/components/pdf-code-check-failed-notice.js b/services/web/frontend/js/features/preview/components/pdf-code-check-failed-notice.js new file mode 100644 index 0000000000..05d1d93b2b --- /dev/null +++ b/services/web/frontend/js/features/preview/components/pdf-code-check-failed-notice.js @@ -0,0 +1,22 @@ +import { memo } from 'react' +import { useTranslation } from 'react-i18next' +import Icon from '../../../shared/components/icon' + +function PdfCodeCheckFailedNotice() { + const { t } = useTranslation() + + return ( +
+
+
+ +
+

+ {t('code_check_failed_explanation')} +

+
+
+ ) +} + +export default memo(PdfCodeCheckFailedNotice) diff --git a/services/web/frontend/js/features/preview/components/pdf-logs-pane-info-notice.js b/services/web/frontend/js/features/preview/components/pdf-logs-pane-info-notice.js new file mode 100644 index 0000000000..0cf493a46d --- /dev/null +++ b/services/web/frontend/js/features/preview/components/pdf-logs-pane-info-notice.js @@ -0,0 +1,49 @@ +import { memo } from 'react' +import { useTranslation } from 'react-i18next' +import usePersistedState from '../../../shared/hooks/use-persisted-state' + +function PdfLogsPaneInfoNotice() { + const { t } = useTranslation() + + const [dismissed, setDismissed] = usePersistedState( + 'logs_pane.dismissed_info_notice', + false + ) + + if (dismissed) { + return null + } + + return ( +
+
+
+ +
+

+ {t('logs_pane_info_message')} +

+ + + {t('give_feedback')} + + + +
+
+ ) +} + +export default memo(PdfLogsPaneInfoNotice) diff --git a/services/web/frontend/js/features/preview/components/preview-logs-pane-entry.js b/services/web/frontend/js/features/preview/components/preview-logs-pane-entry.js index ff5baf43bc..8f0ae3831b 100644 --- a/services/web/frontend/js/features/preview/components/preview-logs-pane-entry.js +++ b/services/web/frontend/js/features/preview/components/preview-logs-pane-entry.js @@ -78,6 +78,16 @@ function PreviewLogEntryContent({ return (
+ {formattedContent ? ( +
{formattedContent}
+ ) : null} + {extraInfoURL ? ( + + ) : null} {rawContent ? (
@@ -104,16 +114,6 @@ function PreviewLogEntryContent({ ) : null}
) : null} - {formattedContent ? ( -
{formattedContent}
- ) : null} - {extraInfoURL ? ( - - ) : null}
) } diff --git a/services/web/frontend/stories/pdf-preview.stories.js b/services/web/frontend/stories/pdf-preview.stories.js index 609c45b533..a0725978c1 100644 --- a/services/web/frontend/stories/pdf-preview.stories.js +++ b/services/web/frontend/stories/pdf-preview.stories.js @@ -14,6 +14,7 @@ import { buildFileList } from '../js/features/pdf-preview/util/file-list' import PdfLogsViewer from '../js/features/pdf-preview/components/pdf-logs-viewer' import examplePdf from './fixtures/storybook-example.pdf' import PdfPreviewError from '../js/features/pdf-preview/components/pdf-preview-error' +import PdfPreviewHybridToolbar from '../js/features/pdf-preview/components/pdf-preview-hybrid-toolbar' setupContext() @@ -22,6 +23,7 @@ export default { component: PdfPreview, subcomponents: { PdfPreviewToolbar, + PdfPreviewHybridToolbar, PdfFileList, PdfPreviewError, }, @@ -496,6 +498,22 @@ export const Toolbar = () => { ) } +export const HybridToolbar = () => { + useFetchMock(fetchMock => { + mockCompile(fetchMock, 500) + mockBuildFile(fetchMock) + }) + + return withContextRoot( + +
+ +
+
, + scope + ) +} + export const FileList = () => { const fileList = useMemo(() => { return buildFileList(outputFiles) diff --git a/services/web/frontend/stylesheets/app/editor/logs.less b/services/web/frontend/stylesheets/app/editor/logs.less index 90eda75052..86bcf15b0b 100644 --- a/services/web/frontend/stylesheets/app/editor/logs.less +++ b/services/web/frontend/stylesheets/app/editor/logs.less @@ -137,6 +137,7 @@ background-color: @ol-blue-gray-1; border-radius: @border-radius-base; overflow: hidden; + margin-top: @margin-sm; } .log-entry-content-raw { diff --git a/services/web/frontend/stylesheets/app/editor/pdf.less b/services/web/frontend/stylesheets/app/editor/pdf.less index b395a05a0c..f8bac33a70 100644 --- a/services/web/frontend/stylesheets/app/editor/pdf.less +++ b/services/web/frontend/stylesheets/app/editor/pdf.less @@ -12,6 +12,7 @@ .toolbar-small-mixin; .toolbar-alt-mixin; padding-right: 5px; + margin-left: 0; &.changes-to-autocompile { // prettier-ignore #gradient > .striped(@color: rgba(255, 255, 255, 0.1), @angle: -45deg); @@ -56,6 +57,61 @@ padding-left: @line-height-computed / 4; } +.toolbar-pdf-hybrid { + .btn:not(.btn-recompile) { + display: inline-block; + color: @toolbar-btn-color; + background-color: transparent; + padding: 4px 2px; + line-height: 1; + height: 24px; + border-radius: 2px; + + &:hover, + &:active, + &:focus { + color: @toolbar-btn-color; + } + + .label { + position: absolute; + top: 0; + right: 0; + padding: 0.15em 0.6em 0.2em; + font-size: 60%; + pointer-events: none; + } + + .btn { + display: inline-block; + color: #fff; + background-color: transparent; + padding: 4px 2px; + line-height: 1; + height: 24px; + border-radius: 2px; + } + + &.log-btn { + margin-right: 3px; + + &.active { + color: white; + background-color: @link-color; + box-shadow: @toolbar-icon-btn-hover-boxshadow; + + &:hover { + color: white; + } + } + + &:focus { + outline: none; + } + } + } +} + .pdf { background-color: @pdf-bg; } diff --git a/services/web/test/frontend/features/pdf-preview/components/pdf-preview.test.js b/services/web/test/frontend/features/pdf-preview/components/pdf-preview.test.js index ee735e3e1b..4a6c1329ba 100644 --- a/services/web/test/frontend/features/pdf-preview/components/pdf-preview.test.js +++ b/services/web/test/frontend/features/pdf-preview/components/pdf-preview.test.js @@ -317,7 +317,7 @@ describe('', function () { await screen.findByRole('button', { name: 'Recompile' }) const logsButton = screen.getByRole('button', { - name: 'This project has an error', + name: 'View logs', }) logsButton.click() @@ -350,7 +350,7 @@ describe('', function () { // show the logs UI const logsButton = screen.getByRole('button', { - name: 'This project has an error', + name: 'View logs', }) logsButton.click()