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()