From ac48d8198715847be12fcd9e8d6ce20c16fb75d4 Mon Sep 17 00:00:00 2001 From: Miguel Serrano Date: Wed, 6 Mar 2024 10:30:32 +0100 Subject: [PATCH] Merge pull request #17373 from overleaf/jpa-figure-modal-options [web] hide figure modal import options as configured server side GitOrigin-RevId: f8907a33d413fcac238b00328eaba14d64f2f31b --- services/web/cypress/support/ct/window.ts | 3 + .../figure-modal-source-picker.tsx | 33 +++-- .../figure-modal-other-project-source.tsx | 38 +++--- .../toolbar/insert-figure-dropdown.tsx | 35 ++++-- .../web/frontend/stories/decorators/scope.tsx | 2 +- .../stylesheets/app/editor/figure-modal.less | 14 +-- .../components/figure-modal.spec.tsx | 117 ++++++++++++++++-- 7 files changed, 183 insertions(+), 59 deletions(-) diff --git a/services/web/cypress/support/ct/window.ts b/services/web/cypress/support/ct/window.ts index 7f1ab17b4a..fcdcc79479 100644 --- a/services/web/cypress/support/ct/window.ts +++ b/services/web/cypress/support/ct/window.ts @@ -4,4 +4,7 @@ window.ExposedSettings = { validRootDocExtensions: ['tex', 'Rtex', 'ltx', 'Rnw'], fileIgnorePattern: '**/{{__MACOSX,.git,.texpadtmp,.R}{,/**},.!(latexmkrc),*.{dvi,aux,log,toc,out,pdfsync,synctex,synctex(busy),fdb_latexmk,fls,nlo,ind,glo,gls,glg,bbl,blg,doc,docx,gz,swp}}', + hasLinkedProjectFileFeature: true, + hasLinkedProjectOutputFileFeature: true, + hasLinkUrlFeature: true, } as typeof window.ExposedSettings diff --git a/services/web/frontend/js/features/source-editor/components/figure-modal/figure-modal-source-picker.tsx b/services/web/frontend/js/features/source-editor/components/figure-modal/figure-modal-source-picker.tsx index b09f6af94c..aa042e4f9b 100644 --- a/services/web/frontend/js/features/source-editor/components/figure-modal/figure-modal-source-picker.tsx +++ b/services/web/frontend/js/features/source-editor/components/figure-modal/figure-modal-source-picker.tsx @@ -9,9 +9,14 @@ import { useTranslation } from 'react-i18next' export const FigureModalSourcePicker: FC = () => { const { t } = useTranslation() + const { + hasLinkedProjectFileFeature, + hasLinkedProjectOutputFileFeature, + hasLinkUrlFeature, + } = window.ExposedSettings return (
-
+
{ title={t('replace_from_project_files')} icon="archive" /> -
-
- - + {(hasLinkedProjectFileFeature || hasLinkedProjectOutputFileFeature) && ( + + )} + {hasLinkUrlFeature && ( + + )}
) diff --git a/services/web/frontend/js/features/source-editor/components/figure-modal/file-sources/figure-modal-other-project-source.tsx b/services/web/frontend/js/features/source-editor/components/figure-modal/file-sources/figure-modal-other-project-source.tsx index f997c78d83..ac5981cdc6 100644 --- a/services/web/frontend/js/features/source-editor/components/figure-modal/file-sources/figure-modal-other-project-source.tsx +++ b/services/web/frontend/js/features/source-editor/components/figure-modal/file-sources/figure-modal-other-project-source.tsx @@ -35,7 +35,11 @@ export const FigureModalOtherProjectSource: FC = () => { const { _id: projectId } = useProjectContext() const { loading: projectsLoading, data: projects, error } = useUserProjects() const [selectedProject, setSelectedProject] = useState(null) - const [usingOutputFiles, setUsingOutputFiles] = useState(false) + const { hasLinkedProjectFileFeature, hasLinkedProjectOutputFileFeature } = + window.ExposedSettings + const [usingOutputFiles, setUsingOutputFiles] = useState( + !hasLinkedProjectFileFeature + ) const [nameDirty, setNameDirty] = useState(false) const [name, setName] = useState('') const [folder, setFolder] = useState(null) @@ -158,21 +162,23 @@ export const FigureModalOtherProjectSource: FC = () => { }) }} /> -
- or{' '} - -
+ {hasLinkedProjectFileFeature && hasLinkedProjectOutputFileFeature && ( +
+ or{' '} + +
+ )} From project files - - openFigureModal(FigureModalSource.OTHER_PROJECT, 'other-project') - } - > - From another project - - openFigureModal(FigureModalSource.FROM_URL, 'from-url')} - > - From URL - + {(hasLinkedProjectFileFeature || hasLinkedProjectOutputFileFeature) && ( + + openFigureModal(FigureModalSource.OTHER_PROJECT, 'other-project') + } + > + From another project + + )} + {hasLinkUrlFeature && ( + + openFigureModal(FigureModalSource.FROM_URL, 'from-url') + } + > + From URL + + )} ) }) diff --git a/services/web/frontend/stories/decorators/scope.tsx b/services/web/frontend/stories/decorators/scope.tsx index ea431e2e3a..311655c2ed 100644 --- a/services/web/frontend/stories/decorators/scope.tsx +++ b/services/web/frontend/stories/decorators/scope.tsx @@ -136,7 +136,7 @@ const initialize = () => { emailConfirmationDisabled: false, enableSubscriptions: true, hasAffiliationsFeature: false, - hasLinkUrlFeature: false, + hasLinkUrlFeature: true, hasLinkedProjectFileFeature: true, hasLinkedProjectOutputFileFeature: true, hasSamlFeature: true, diff --git a/services/web/frontend/stylesheets/app/editor/figure-modal.less b/services/web/frontend/stylesheets/app/editor/figure-modal.less index 8561d0aa18..ab31a3365d 100644 --- a/services/web/frontend/stylesheets/app/editor/figure-modal.less +++ b/services/web/frontend/stylesheets/app/editor/figure-modal.less @@ -99,9 +99,11 @@ cursor: pointer; } -.figure-modal-source-button-row { - display: flex; +.figure-modal-source-button-grid { + display: grid; justify-content: space-between; + gap: 8px; + grid-template-columns: 1fr 1fr; margin: 0 auto; &:not(:first-of-type) { @@ -120,14 +122,6 @@ border: none; padding: 0 8px; - &:nth-child(even) { - margin-left: 4px; - } - - &:nth-child(odd) { - margin-right: 4px; - } - &-title { flex: 1 1 auto; text-align: left; diff --git a/services/web/test/frontend/features/source-editor/components/figure-modal.spec.tsx b/services/web/test/frontend/features/source-editor/components/figure-modal.spec.tsx index 88aff2d5c3..0a868f8733 100644 --- a/services/web/test/frontend/features/source-editor/components/figure-modal.spec.tsx +++ b/services/web/test/frontend/features/source-editor/components/figure-modal.spec.tsx @@ -3,6 +3,7 @@ import { EditorProviders } from '../../../helpers/editor-providers' import { mockScope, rootFolderId } from '../helpers/mock-scope' import { FC } from 'react' import { FileTreePathContext } from '@/features/file-tree/contexts/file-tree-path' +import { ExposedSettings } from '../../../../../types/exposed-settings' const Container: FC = ({ children }) => (
{children}
@@ -40,14 +41,8 @@ const matchUrl = (urlToMatch: RegExp | string) => describe('', function () { // TODO: rewrite these tests to be in source mode when toolbar is added there // TODO: Write tests for width toggle, when we can match on source code - beforeEach(function () { - window.metaAttributesCache.set('ol-preventCompileOnLoad', true) - cy.interceptMathJax() - cy.interceptEvents() - cy.interceptSpelling() - + function mount() { const content = '' - const scope = mockScope(content) scope.editor.showVisual = true @@ -74,6 +69,22 @@ describe('', function () { ) + } + + let previousExposedSettings: ExposedSettings + before(function () { + previousExposedSettings = window.ExposedSettings + }) + afterEach(function () { + window.ExposedSettings = previousExposedSettings + }) + + beforeEach(function () { + window.metaAttributesCache.set('ol-preventCompileOnLoad', true) + cy.interceptMathJax() + cy.interceptEvents() + cy.interceptSpelling() + mount() }) describe('Upload from computer source', function () { @@ -258,6 +269,98 @@ describe('', function () { }) }) + describe('Feature flags', function () { + describe('with hasLinkUrlFeature=false', function () { + beforeEach(function () { + window.ExposedSettings = Object.assign({}, previousExposedSettings, { + hasLinkedProjectFileFeature: true, + hasLinkedProjectOutputFileFeature: true, + hasLinkUrlFeature: false, + }) + mount() + clickToolbarButton('Insert Figure') + }) + it('should not have import from url option', function () { + cy.findByRole('menu').within(() => { + cy.findByText('From URL').should('not.exist') + }) + }) + }) + describe('with hasLinkedProjectFileFeature=false and hasLinkedProjectOutputFileFeature=false', function () { + beforeEach(function () { + window.ExposedSettings = Object.assign({}, previousExposedSettings, { + hasLinkedProjectFileFeature: false, + hasLinkedProjectOutputFileFeature: false, + hasLinkUrlFeature: true, + }) + mount() + clickToolbarButton('Insert Figure') + }) + it('should not have import from project file option', function () { + cy.findByRole('menu').within(() => { + cy.findByText('From another project').should('not.exist') + }) + }) + }) + + function setupFromAnotherProject() { + mount() + cy.interceptProjectListing() + clickToolbarButton('Insert Figure') + cy.findByRole('menu').within(() => { + cy.findByText('From another project').click() + }) + cy.findByText('Select a project').click() + cy.findByRole('listbox').within(() => { + cy.findByText('My first project').click() + }) + } + function expectNoOutputSwitch() { + it('should hide output switch', function () { + cy.findByText('select from output files').should('not.exist') + cy.findByText('select from source files').should('not.exist') + }) + } + + describe('with hasLinkedProjectFileFeature=false', function () { + beforeEach(function () { + window.ExposedSettings = Object.assign({}, previousExposedSettings, { + hasLinkedProjectFileFeature: false, + hasLinkedProjectOutputFileFeature: true, + hasLinkUrlFeature: true, + }) + cy.interceptCompile() + setupFromAnotherProject() + }) + expectNoOutputSwitch() + it('should show output file selector', function () { + cy.findByText('Select an output file').click() + cy.findByRole('listbox').within(() => { + cy.findByText('output.pdf').click() + }) + }) + }) + + describe('with hasLinkedProjectOutputFileFeature=false', function () { + beforeEach(function () { + window.ExposedSettings = Object.assign({}, previousExposedSettings, { + hasLinkedProjectFileFeature: true, + hasLinkedProjectOutputFileFeature: false, + hasLinkUrlFeature: true, + }) + setupFromAnotherProject() + }) + expectNoOutputSwitch() + + it('should show source file selector', function () { + cy.findByText('Select a file').click() + cy.findByRole('listbox').within(() => { + cy.findByText('frog.jpg').click() + }) + }) + }) + }) + describe('From URL source', function () { beforeEach(function () { cy.interceptLinkedFile()