From 7face21313a1fca9915fefa0535ed6da20b601c5 Mon Sep 17 00:00:00 2001 From: Antoine Clausse Date: Thu, 10 Oct 2024 09:38:28 +0200 Subject: [PATCH] [web] Migrate the file view to Bootstrap 5 (#20765) * [web] Remove unnecessary divs around `fileInfo` * [web] Add file-view SCSS style * [web] Simplify `TPRFileViewInfo` * [web] Add div for action buttons * [web] Misc. simplifications * [web] Add Overleaf logo in bg when selecting multiple files * [web] Add message when multiple files are selected * [web] Add .full-size class * [web] Import styles from LESS file * [web] Update icons, use MaterialIcon * [web] Use OLButton * [web] Add missing space between icons and text * [web] Adjust margins * [web] Fix alert button * [web] Update Alerts * [web] Update `FileViewLoadingIndicator` * [web] Fix test "shows a loading indicator..." This was failing because `LoadingSpinner` is shown after a setTimeout. Maybe we can skip this setTimeout when delay==0 ? * [web] Remove Row/Col around error notifications * [web] Replace `!!` by `Boolean` Co-authored-by: ilkin-overleaf <100852799+ilkin-overleaf@users.noreply.github.com> * [web] Use `alert` class in BS3 only Co-authored-by: Ilkin Ismailov * [web] Update "Go to settings" to OLButton Co-authored-by: Ilkin Ismailov * [web] Use `BootstrapVersionSwitcher` instead of `isBootstrap5` Co-authored-by: Ilkin Ismailov * [web] Align Alert content to the left in BS5 * [web] Remove `leadingIcon` on Refresh buttons * [web] Make the download link be an OLButton * [web] Set `tpr-refresh-error` in BS3 only Co-authored-by: Rebeka Dekany <50901361+rebekadekany@users.noreply.github.com> * [web] Use `var(--white);` instead of `white` Co-authored-by: Rebeka * [web] Update OLButton size (small -> sm) --------- Co-authored-by: ilkin-overleaf <100852799+ilkin-overleaf@users.noreply.github.com> Co-authored-by: Ilkin Ismailov Co-authored-by: Rebeka Dekany <50901361+rebekadekany@users.noreply.github.com> Co-authored-by: Rebeka GitOrigin-RevId: 04f369c0f1a53d47619a1570648ee58de5050751 --- .../file-view/components/file-view-header.tsx | 69 ++++++------ .../file-view/components/file-view-icons.jsx | 24 ++++- .../components/file-view-refresh-button.tsx | 19 +++- .../components/file-view-refresh-error.tsx | 15 ++- .../file-view/components/file-view-text.tsx | 16 ++- .../file-view/components/file-view.jsx | 13 ++- .../ui/components/types/button-props.ts | 2 +- .../stylesheets/app/editor/file-view.less | 32 +++++- .../stylesheets/bootstrap-5/pages/all.scss | 1 + .../bootstrap-5/pages/editor/file-view.scss | 102 ++++++++++++++++++ .../file-view/components/file-view.test.jsx | 4 +- 11 files changed, 223 insertions(+), 74 deletions(-) create mode 100644 services/web/frontend/stylesheets/bootstrap-5/pages/editor/file-view.scss diff --git a/services/web/frontend/js/features/file-view/components/file-view-header.tsx b/services/web/frontend/js/features/file-view/components/file-view-header.tsx index 0e0a8ab4ea..04ff106db7 100644 --- a/services/web/frontend/js/features/file-view/components/file-view-header.tsx +++ b/services/web/frontend/js/features/file-view/components/file-view-header.tsx @@ -4,7 +4,7 @@ import { Trans, useTranslation } from 'react-i18next' import Icon from '../../../shared/components/icon' import { formatTime, relativeDate } from '../../utils/format-date' import { useFileTreeData } from '@/shared/context/file-tree-data-context' -import { useProjectContext } from '../../../shared/context/project-context' +import { useProjectContext } from '@/shared/context/project-context' import { Nullable } from '../../../../../types/utils' import importOverleafModules from '../../../../macros/import-overleaf-module.macro' @@ -13,6 +13,9 @@ import { BinaryFile, hasProvider, LinkedFile } from '../types/binary-file' import FileViewRefreshButton from './file-view-refresh-button' import FileViewRefreshError from './file-view-refresh-error' import { useSnapshotContext } from '@/features/ide-react/context/snapshot-context' +import MaterialIcon from '@/shared/components/material-icon' +import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher' +import OLButton from '@/features/ui/components/ol/ol-button' const tprFileViewInfo = importOverleafModules('tprFileViewInfo') as { import: { TPRFileViewInfo: ElementType } @@ -58,50 +61,44 @@ export default function FileViewHeader({ file }: FileViewHeaderProps) { let fileInfo if (file.linkedFileData) { if (hasProvider(file, 'url')) { - fileInfo = ( -
- -
- ) + fileInfo = } else if (hasProvider(file, 'project_file')) { - fileInfo = ( -
- -
- ) + fileInfo = } else if (hasProvider(file, 'project_output_file')) { - fileInfo = ( -
- -
- ) + fileInfo = } } return ( -
+ <> {file.linkedFileData && fileInfo} {file.linkedFileData && tprFileViewInfo.map(({ import: { TPRFileViewInfo }, path }) => ( ))} - {file.linkedFileData && !fileTreeReadOnly && ( - - )} -   - - -   - {t('download')} - +
+ {file.linkedFileData && !fileTreeReadOnly && ( + + )} + + } + bs5={} + />{' '} + {t('download')} + +
{file.linkedFileData && tprFileViewNotOriginalImporter.map( ({ import: { TPRFileViewNotOriginalImporter }, path }) => ( @@ -111,7 +108,7 @@ export default function FileViewHeader({ file }: FileViewHeaderProps) { {refreshError && ( )} -
+ ) } @@ -150,7 +147,7 @@ function ProjectFilePathProvider({ file }: ProjectFilePathProviderProps) { /* eslint-disable jsx-a11y/anchor-has-content, react/jsx-key */ return (

- + {' '} { return ( - + } + bs5={ + + } /> ) } diff --git a/services/web/frontend/js/features/file-view/components/file-view-refresh-button.tsx b/services/web/frontend/js/features/file-view/components/file-view-refresh-button.tsx index be6e68ccc0..46721c7520 100644 --- a/services/web/frontend/js/features/file-view/components/file-view-refresh-button.tsx +++ b/services/web/frontend/js/features/file-view/components/file-view-refresh-button.tsx @@ -13,6 +13,7 @@ import useAbortController from '@/shared/hooks/use-abort-controller' import type { BinaryFile } from '../types/binary-file' import { Nullable } from '../../../../../types/utils' import importOverleafModules from '../../../../macros/import-overleaf-module.macro' +import OLButton from '@/features/ui/components/ol/ol-button' type FileViewRefreshButtonProps = { setRefreshError: Dispatch>> @@ -90,13 +91,21 @@ function FileViewRefreshButtonDefault({ const { t } = useTranslation() return ( - + {t('refresh')} + ) } diff --git a/services/web/frontend/js/features/file-view/components/file-view-refresh-error.tsx b/services/web/frontend/js/features/file-view/components/file-view-refresh-error.tsx index ebc126ae19..363c663988 100644 --- a/services/web/frontend/js/features/file-view/components/file-view-refresh-error.tsx +++ b/services/web/frontend/js/features/file-view/components/file-view-refresh-error.tsx @@ -2,6 +2,7 @@ import type { ElementType } from 'react' import { useTranslation } from 'react-i18next' import importOverleafModules from '../../../../macros/import-overleaf-module.macro' import { BinaryFile } from '../types/binary-file' +import OLNotification from '@/features/ui/components/ol/ol-notification' type FileViewRefreshErrorProps = { file: BinaryFile @@ -33,11 +34,15 @@ export default function FileViewRefreshError({ )[0] } else { return ( -

-
-
- {t('access_denied')}: {refreshError} -
+
+ + {t('access_denied')}: {refreshError} + + } + />
) } diff --git a/services/web/frontend/js/features/file-view/components/file-view-text.tsx b/services/web/frontend/js/features/file-view/components/file-view-text.tsx index cf3f2263dd..ddc5a216ef 100644 --- a/services/web/frontend/js/features/file-view/components/file-view-text.tsx +++ b/services/web/frontend/js/features/file-view/components/file-view-text.tsx @@ -93,15 +93,13 @@ export default function FileViewText({ fetchDataController, ]) return ( -
- {textPreview && ( -
-
-

{textPreview}

- {shouldShowDots &&

...

} -
+ Boolean(textPreview) && ( +
+
+

{textPreview}

+ {shouldShowDots &&

...

}
- )} -
+
+ ) ) } diff --git a/services/web/frontend/js/features/file-view/components/file-view.jsx b/services/web/frontend/js/features/file-view/components/file-view.jsx index bb567bb8c5..711d125fe9 100644 --- a/services/web/frontend/js/features/file-view/components/file-view.jsx +++ b/services/web/frontend/js/features/file-view/components/file-view.jsx @@ -6,7 +6,7 @@ import FileViewHeader from './file-view-header' import FileViewImage from './file-view-image' import FileViewPdf from './file-view-pdf' import FileViewText from './file-view-text' -import Icon from '../../../shared/components/icon' +import LoadingSpinner from '@/shared/components/loading-spinner' import getMeta from '@/utils/meta' const imageExtensions = ['png', 'jpg', 'jpeg', 'gif'] @@ -71,13 +71,12 @@ export default function FileView({ file }) { } function FileViewLoadingIndicator() { - const { t } = useTranslation() return ( -
- - -   {t('loading')}… - +
+
) } diff --git a/services/web/frontend/js/features/ui/components/types/button-props.ts b/services/web/frontend/js/features/ui/components/types/button-props.ts index 07ff9c2a9c..433655ae8a 100644 --- a/services/web/frontend/js/features/ui/components/types/button-props.ts +++ b/services/web/frontend/js/features/ui/components/types/button-props.ts @@ -4,7 +4,7 @@ export type ButtonProps = { children?: ReactNode className?: string disabled?: boolean - download?: boolean + download?: boolean | string draggable?: boolean form?: string leadingIcon?: string | React.ReactNode diff --git a/services/web/frontend/stylesheets/app/editor/file-view.less b/services/web/frontend/stylesheets/app/editor/file-view.less index 573d86da41..552305870e 100644 --- a/services/web/frontend/stylesheets/app/editor/file-view.less +++ b/services/web/frontend/stylesheets/app/editor/file-view.less @@ -4,6 +4,33 @@ background-color: @gray-lightest; text-align: center; overflow: auto; + + .file-view-buttons { + display: flex; + flex-wrap: wrap; + gap: @spacing-03; + justify-content: center; + } + + .file-view-error { + margin: @spacing-08 -15px auto; + + > .alert { + max-width: 400px; + margin: 0 auto; + } + } + + .tpr-refresh-error { + .btn { + color: @neutral-90; + background-color: @white; + &:hover { + background-color: @neutral-20; + } + } + } + img, .file-view-pdf { max-width: 100%; @@ -43,10 +70,7 @@ line-height: 1.1em; overflow: auto; border: 1px solid @gray-lighter; - padding-left: 12px; - padding-right: 12px; - padding-top: 8px; - padding-bottom: 8px; + padding: 8px 12px; text-align: left; white-space: pre; font-family: monospace; diff --git a/services/web/frontend/stylesheets/bootstrap-5/pages/all.scss b/services/web/frontend/stylesheets/bootstrap-5/pages/all.scss index 6668a9ba9a..ca015ca871 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/pages/all.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/pages/all.scss @@ -10,6 +10,7 @@ @import 'editor/loading-screen'; @import 'editor/outline'; @import 'editor/file-tree'; +@import 'editor/file-view'; @import 'editor/figure-modal'; @import 'editor/chat'; @import 'subscription'; diff --git a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/file-view.scss b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/file-view.scss new file mode 100644 index 0000000000..4a65914c4a --- /dev/null +++ b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/file-view.scss @@ -0,0 +1,102 @@ +.file-view { + padding: var(--spacing-05); + text-align: center; + overflow: auto; + + .loading-panel { + padding-top: 8rem; + background: var(--neutral-10); + } + + .file-view-buttons { + display: flex; + flex-wrap: wrap; + gap: var(--spacing-03); + justify-content: center; + } + + .file-view-error { + margin: var(--spacing-08) auto auto; + max-width: 400px; + text-align: left; + } + + img, + .file-view-pdf { + max-width: 100%; + max-height: 90%; + display: block; + margin: var(--spacing-05) auto auto; + border: 1px solid var(--neutral-60); + box-shadow: 0 2px 3px var(--neutral-60); + background-color: var(--white); + } + + .file-view-pdf { + overflow: auto; + width: max-content; + display: flex; + flex-direction: column; + align-items: center; + + .pdf-page:not(:last-of-type) { + border-bottom: 1px solid var(--neutral-60); + } + } + + .linked-file-icon { + color: var(--blue-50); + } + + .no-preview { + color: var(--neutral-60); + font-size: var(--font-size-06); + margin-top: var(--spacing-06); + } + + .text-preview { + margin-top: var(--spacing-05); + + .scroll-container { + background-color: var(--white); + font-size: 0.8em; + line-height: 1.1em; + overflow: auto; + border: 1px solid var(--neutral-30); + padding: var(--spacing-04) var(--spacing-05); + text-align: left; + white-space: pre; + font-family: monospace; + } + } +} + +.full-size, +.loading-panel { + position: absolute; + inset: 0; +} + +.no-history-available, +.no-file-selection-message, +.multi-selection-message { + width: 50%; + margin: var(--spacing-10) auto; + text-align: center; +} + +.pdf-empty, +.no-history-available, +.no-file-selection, +.multi-selection-ongoing { + &::before { + @extend .full-size; + + left: 20px; + content: ''; + background: url(../../../../../public/img/ol-brand/overleaf-o-grey.svg) + center / 200px no-repeat; + opacity: 0.2; + pointer-events: none; + } +} diff --git a/services/web/test/frontend/features/file-view/components/file-view.test.jsx b/services/web/test/frontend/features/file-view/components/file-view.test.jsx index 5a97e36629..638b5d40ea 100644 --- a/services/web/test/frontend/features/file-view/components/file-view.test.jsx +++ b/services/web/test/frontend/features/file-view/components/file-view.test.jsx @@ -48,7 +48,7 @@ describe('', function () { renderWithEditorContext() await waitForElementToBeRemoved(() => - screen.getByText('Loading', { exact: false }) + screen.getByTestId('loading-panel-file-view') ) }) @@ -70,7 +70,7 @@ describe('', function () { it('shows a loading indicator while the file is loading', async function () { renderWithEditorContext() - screen.getByText('Loading', { exact: false }) + screen.getByTestId('loading-panel-file-view') }) it('shows messaging if the image could not be loaded', async function () {