Procházet zdrojové kódy

Merge pull request #17373 from overleaf/jpa-figure-modal-options

[web] hide figure modal import options as configured server side

GitOrigin-RevId: f8907a33d413fcac238b00328eaba14d64f2f31b
Miguel Serrano před 2 roky
rodič
revize
ac48d81987

+ 3 - 0
services/web/cypress/support/ct/window.ts

@@ -4,4 +4,7 @@ window.ExposedSettings = {
   validRootDocExtensions: ['tex', 'Rtex', 'ltx', 'Rnw'],
   validRootDocExtensions: ['tex', 'Rtex', 'ltx', 'Rnw'],
   fileIgnorePattern:
   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}}',
     '**/{{__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
 } as typeof window.ExposedSettings

+ 20 - 13
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 = () => {
 export const FigureModalSourcePicker: FC = () => {
   const { t } = useTranslation()
   const { t } = useTranslation()
+  const {
+    hasLinkedProjectFileFeature,
+    hasLinkedProjectOutputFileFeature,
+    hasLinkUrlFeature,
+  } = window.ExposedSettings
   return (
   return (
     <div className="figure-modal-source-selector">
     <div className="figure-modal-source-selector">
-      <div className="figure-modal-source-button-row">
+      <div className="figure-modal-source-button-grid">
         <FigureModalSourceButton
         <FigureModalSourceButton
           type={FigureModalSource.FILE_UPLOAD}
           type={FigureModalSource.FILE_UPLOAD}
           title={t('replace_from_computer')}
           title={t('replace_from_computer')}
@@ -22,18 +27,20 @@ export const FigureModalSourcePicker: FC = () => {
           title={t('replace_from_project_files')}
           title={t('replace_from_project_files')}
           icon="archive"
           icon="archive"
         />
         />
-      </div>
-      <div className="figure-modal-source-button-row">
-        <FigureModalSourceButton
-          type={FigureModalSource.OTHER_PROJECT}
-          title={t('replace_from_another_project')}
-          icon="folder-open"
-        />
-        <FigureModalSourceButton
-          type={FigureModalSource.FROM_URL}
-          title={t('replace_from_url')}
-          icon="globe"
-        />
+        {(hasLinkedProjectFileFeature || hasLinkedProjectOutputFileFeature) && (
+          <FigureModalSourceButton
+            type={FigureModalSource.OTHER_PROJECT}
+            title={t('replace_from_another_project')}
+            icon="folder-open"
+          />
+        )}
+        {hasLinkUrlFeature && (
+          <FigureModalSourceButton
+            type={FigureModalSource.FROM_URL}
+            title={t('replace_from_url')}
+            icon="globe"
+          />
+        )}
       </div>
       </div>
     </div>
     </div>
   )
   )

+ 22 - 16
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 { _id: projectId } = useProjectContext()
   const { loading: projectsLoading, data: projects, error } = useUserProjects()
   const { loading: projectsLoading, data: projects, error } = useUserProjects()
   const [selectedProject, setSelectedProject] = useState<null | Project>(null)
   const [selectedProject, setSelectedProject] = useState<null | Project>(null)
-  const [usingOutputFiles, setUsingOutputFiles] = useState<boolean>(false)
+  const { hasLinkedProjectFileFeature, hasLinkedProjectOutputFileFeature } =
+    window.ExposedSettings
+  const [usingOutputFiles, setUsingOutputFiles] = useState<boolean>(
+    !hasLinkedProjectFileFeature
+  )
   const [nameDirty, setNameDirty] = useState<boolean>(false)
   const [nameDirty, setNameDirty] = useState<boolean>(false)
   const [name, setName] = useState<string>('')
   const [name, setName] = useState<string>('')
   const [folder, setFolder] = useState<File | null>(null)
   const [folder, setFolder] = useState<File | null>(null)
@@ -158,21 +162,23 @@ export const FigureModalOtherProjectSource: FC = () => {
           })
           })
         }}
         }}
       />
       />
-      <div>
-        or{' '}
-        <Button
-          className="p-0"
-          bsStyle="link"
-          type="button"
-          onClick={() => setUsingOutputFiles(value => !value)}
-        >
-          <span>
-            {usingOutputFiles
-              ? t('select_from_project_files')
-              : t('select_from_output_files')}
-          </span>
-        </Button>
-      </div>
+      {hasLinkedProjectFileFeature && hasLinkedProjectOutputFileFeature && (
+        <div>
+          or{' '}
+          <Button
+            className="p-0"
+            bsStyle="link"
+            type="button"
+            onClick={() => setUsingOutputFiles(value => !value)}
+          >
+            <span>
+              {usingOutputFiles
+                ? t('select_from_project_files')
+                : t('select_from_output_files')}
+            </span>
+          </Button>
+        </div>
+      )}
       <FileRelocator
       <FileRelocator
         folder={folder}
         folder={folder}
         name={name}
         name={name}

+ 23 - 12
services/web/frontend/js/features/source-editor/components/toolbar/insert-figure-dropdown.tsx

@@ -22,6 +22,11 @@ export const InsertFigureDropdown = memo(function InsertFigureDropdown() {
     },
     },
     [view]
     [view]
   )
   )
+  const {
+    hasLinkedProjectFileFeature,
+    hasLinkedProjectOutputFileFeature,
+    hasLinkUrlFeature,
+  } = window.ExposedSettings
   return (
   return (
     <ToolbarButtonMenu
     <ToolbarButtonMenu
       id="toolbar-figure"
       id="toolbar-figure"
@@ -43,18 +48,24 @@ export const InsertFigureDropdown = memo(function InsertFigureDropdown() {
       >
       >
         <Icon type="archive" fw /> From project files
         <Icon type="archive" fw /> From project files
       </ListGroupItem>
       </ListGroupItem>
-      <ListGroupItem
-        onClick={() =>
-          openFigureModal(FigureModalSource.OTHER_PROJECT, 'other-project')
-        }
-      >
-        <Icon type="folder-open" fw /> From another project
-      </ListGroupItem>
-      <ListGroupItem
-        onClick={() => openFigureModal(FigureModalSource.FROM_URL, 'from-url')}
-      >
-        <Icon type="globe" fw /> From URL
-      </ListGroupItem>
+      {(hasLinkedProjectFileFeature || hasLinkedProjectOutputFileFeature) && (
+        <ListGroupItem
+          onClick={() =>
+            openFigureModal(FigureModalSource.OTHER_PROJECT, 'other-project')
+          }
+        >
+          <Icon type="folder-open" fw /> From another project
+        </ListGroupItem>
+      )}
+      {hasLinkUrlFeature && (
+        <ListGroupItem
+          onClick={() =>
+            openFigureModal(FigureModalSource.FROM_URL, 'from-url')
+          }
+        >
+          <Icon type="globe" fw /> From URL
+        </ListGroupItem>
+      )}
     </ToolbarButtonMenu>
     </ToolbarButtonMenu>
   )
   )
 })
 })

+ 1 - 1
services/web/frontend/stories/decorators/scope.tsx

@@ -136,7 +136,7 @@ const initialize = () => {
     emailConfirmationDisabled: false,
     emailConfirmationDisabled: false,
     enableSubscriptions: true,
     enableSubscriptions: true,
     hasAffiliationsFeature: false,
     hasAffiliationsFeature: false,
-    hasLinkUrlFeature: false,
+    hasLinkUrlFeature: true,
     hasLinkedProjectFileFeature: true,
     hasLinkedProjectFileFeature: true,
     hasLinkedProjectOutputFileFeature: true,
     hasLinkedProjectOutputFileFeature: true,
     hasSamlFeature: true,
     hasSamlFeature: true,

+ 4 - 10
services/web/frontend/stylesheets/app/editor/figure-modal.less

@@ -99,9 +99,11 @@
   cursor: pointer;
   cursor: pointer;
 }
 }
 
 
-.figure-modal-source-button-row {
-  display: flex;
+.figure-modal-source-button-grid {
+  display: grid;
   justify-content: space-between;
   justify-content: space-between;
+  gap: 8px;
+  grid-template-columns: 1fr 1fr;
   margin: 0 auto;
   margin: 0 auto;
 
 
   &:not(:first-of-type) {
   &:not(:first-of-type) {
@@ -120,14 +122,6 @@
   border: none;
   border: none;
   padding: 0 8px;
   padding: 0 8px;
 
 
-  &:nth-child(even) {
-    margin-left: 4px;
-  }
-
-  &:nth-child(odd) {
-    margin-right: 4px;
-  }
-
   &-title {
   &-title {
     flex: 1 1 auto;
     flex: 1 1 auto;
     text-align: left;
     text-align: left;

+ 110 - 7
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 { mockScope, rootFolderId } from '../helpers/mock-scope'
 import { FC } from 'react'
 import { FC } from 'react'
 import { FileTreePathContext } from '@/features/file-tree/contexts/file-tree-path'
 import { FileTreePathContext } from '@/features/file-tree/contexts/file-tree-path'
+import { ExposedSettings } from '../../../../../types/exposed-settings'
 
 
 const Container: FC = ({ children }) => (
 const Container: FC = ({ children }) => (
   <div style={{ width: 1500, height: 785 }}>{children}</div>
   <div style={{ width: 1500, height: 785 }}>{children}</div>
@@ -40,14 +41,8 @@ const matchUrl = (urlToMatch: RegExp | string) =>
 describe('<FigureModal />', function () {
 describe('<FigureModal />', function () {
   // TODO: rewrite these tests to be in source mode when toolbar is added there
   // 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
   // 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 content = ''
-
     const scope = mockScope(content)
     const scope = mockScope(content)
     scope.editor.showVisual = true
     scope.editor.showVisual = true
 
 
@@ -74,6 +69,22 @@ describe('<FigureModal />', function () {
         </EditorProviders>
         </EditorProviders>
       </Container>
       </Container>
     )
     )
+  }
+
+  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 () {
   describe('Upload from computer source', function () {
@@ -258,6 +269,98 @@ describe('<FigureModal />', 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 () {
   describe('From URL source', function () {
     beforeEach(function () {
     beforeEach(function () {
       cy.interceptLinkedFile()
       cy.interceptLinkedFile()