mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Standardise scope/context usage in Storybook stories (#7842)
GitOrigin-RevId: 109a4357fc3b083ffbd3af5b8c98acf0f655f297
This commit is contained in:
parent
cb065e7f12
commit
d91ee50762
35 changed files with 643 additions and 537 deletions
|
@ -9,14 +9,14 @@ import Icon from '../../../shared/components/icon'
|
||||||
|
|
||||||
const imageExtensions = ['png', 'jpg', 'jpeg', 'gif']
|
const imageExtensions = ['png', 'jpg', 'jpeg', 'gif']
|
||||||
|
|
||||||
const textExtensions = window.ExposedSettings.textExtensions
|
|
||||||
|
|
||||||
export default function FileView({ file, storeReferencesKeys }) {
|
export default function FileView({ file, storeReferencesKeys }) {
|
||||||
const [contentLoading, setContentLoading] = useState(true)
|
const [contentLoading, setContentLoading] = useState(true)
|
||||||
const [hasError, setHasError] = useState(false)
|
const [hasError, setHasError] = useState(false)
|
||||||
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const { textExtensions } = window.ExposedSettings
|
||||||
|
|
||||||
const extension = file.name.split('.').pop().toLowerCase()
|
const extension = file.name.split('.').pop().toLowerCase()
|
||||||
const isUnpreviewableFile =
|
const isUnpreviewableFile =
|
||||||
!imageExtensions.includes(extension) && !textExtensions.includes(extension)
|
!imageExtensions.includes(extension) && !textExtensions.includes(extension)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Conditionally enable Sentry based on whether the DSN token is set
|
// Conditionally enable Sentry based on whether the DSN token is set
|
||||||
import getMeta from '../utils/meta'
|
import getMeta from '../utils/meta'
|
||||||
|
|
||||||
const reporterPromise = window.ExposedSettings.sentryDsn
|
const reporterPromise = window.ExposedSettings?.sentryDsn
|
||||||
? sentryReporter()
|
? sentryReporter()
|
||||||
: nullReporter()
|
: nullReporter()
|
||||||
|
|
||||||
|
|
|
@ -1,44 +1,8 @@
|
||||||
import { v4 as uuid } from 'uuid'
|
import { useEffect } from 'react'
|
||||||
|
|
||||||
import { ContextRoot } from '../js/shared/context/root-context'
|
|
||||||
import ChatPane from '../js/features/chat/components/chat-pane'
|
import ChatPane from '../js/features/chat/components/chat-pane'
|
||||||
import { stubMathJax } from '../../test/frontend/features/chat/components/stubs'
|
|
||||||
import useFetchMock from './hooks/use-fetch-mock'
|
import useFetchMock from './hooks/use-fetch-mock'
|
||||||
|
import { generateMessages } from './fixtures/chat-messages'
|
||||||
const ONE_MINUTE = 60 * 1000
|
import { ScopeDecorator } from './decorators/scope'
|
||||||
|
|
||||||
const user = {
|
|
||||||
id: 'fake_user',
|
|
||||||
first_name: 'mortimer',
|
|
||||||
email: 'fake@example.com',
|
|
||||||
}
|
|
||||||
|
|
||||||
const user2 = {
|
|
||||||
id: 'another_fake_user',
|
|
||||||
first_name: 'leopold',
|
|
||||||
email: 'another_fake@example.com',
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateMessages(count) {
|
|
||||||
const messages = []
|
|
||||||
let timestamp = new Date().getTime() // newest message goes first
|
|
||||||
for (let i = 0; i <= count; i++) {
|
|
||||||
const author = Math.random() > 0.5 ? user : user2
|
|
||||||
// modify the timestamp so the previous message has 70% chances to be within 5 minutes from
|
|
||||||
// the current one, for grouping purposes
|
|
||||||
timestamp -= (4.3 + Math.random()) * ONE_MINUTE
|
|
||||||
|
|
||||||
messages.push({
|
|
||||||
id: uuid(),
|
|
||||||
content: `message #${i}`,
|
|
||||||
user: author,
|
|
||||||
timestamp,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return messages
|
|
||||||
}
|
|
||||||
|
|
||||||
stubMathJax()
|
|
||||||
|
|
||||||
export const Conversation = args => {
|
export const Conversation = args => {
|
||||||
useFetchMock(fetchMock => {
|
useFetchMock(fetchMock => {
|
||||||
|
@ -66,6 +30,14 @@ export const Loading = args => {
|
||||||
return <ChatPane {...args} />
|
return <ChatPane {...args} />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const LoadingError = args => {
|
||||||
|
useFetchMock(fetchMock => {
|
||||||
|
fetchMock.get(/messages/, 500)
|
||||||
|
})
|
||||||
|
|
||||||
|
return <ChatPane {...args} />
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Editor / Chat',
|
title: 'Editor / Chat',
|
||||||
component: ChatPane,
|
component: ChatPane,
|
||||||
|
@ -76,13 +48,22 @@ export default {
|
||||||
resetUnreadMessages: () => {},
|
resetUnreadMessages: () => {},
|
||||||
},
|
},
|
||||||
decorators: [
|
decorators: [
|
||||||
Story => (
|
ScopeDecorator,
|
||||||
<>
|
Story => {
|
||||||
<style>{'html, body, .chat { height: 100%; width: 100%; }'}</style>
|
useEffect(() => {
|
||||||
<ContextRoot ide={window._ide} settings={{}}>
|
window.MathJax = {
|
||||||
<Story />
|
Hub: {
|
||||||
</ContextRoot>
|
Queue: () => {},
|
||||||
</>
|
config: { tex2jax: { inlineMath: [['$', '$']] } },
|
||||||
),
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
delete window.MathJax
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return <Story />
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import useFetchMock from './hooks/use-fetch-mock'
|
import useFetchMock from './hooks/use-fetch-mock'
|
||||||
import { withContextRoot } from './utils/with-context-root'
|
|
||||||
import CloneProjectModal from '../js/features/clone-project-modal/components/clone-project-modal'
|
import CloneProjectModal from '../js/features/clone-project-modal/components/clone-project-modal'
|
||||||
|
import { ScopeDecorator } from './decorators/scope'
|
||||||
const project = { _id: 'original-project', name: 'Project Title' }
|
|
||||||
|
|
||||||
export const Success = args => {
|
export const Success = args => {
|
||||||
useFetchMock(fetchMock => {
|
useFetchMock(fetchMock => {
|
||||||
|
@ -13,7 +11,7 @@ export const Success = args => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
return withContextRoot(<CloneProjectModal {...args} />, { project })
|
return <CloneProjectModal {...args} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GenericErrorResponse = args => {
|
export const GenericErrorResponse = args => {
|
||||||
|
@ -25,7 +23,7 @@ export const GenericErrorResponse = args => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
return withContextRoot(<CloneProjectModal {...args} />, { project })
|
return <CloneProjectModal {...args} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SpecificErrorResponse = args => {
|
export const SpecificErrorResponse = args => {
|
||||||
|
@ -37,7 +35,7 @@ export const SpecificErrorResponse = args => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
return withContextRoot(<CloneProjectModal {...args} />, { project })
|
return <CloneProjectModal {...args} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -50,4 +48,5 @@ export default {
|
||||||
handleHide: { action: 'close modal' },
|
handleHide: { action: 'close modal' },
|
||||||
openProject: { action: 'open project' },
|
openProject: { action: 'open project' },
|
||||||
},
|
},
|
||||||
|
decorators: [ScopeDecorator],
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,26 @@
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import useFetchMock from './hooks/use-fetch-mock'
|
import useFetchMock from './hooks/use-fetch-mock'
|
||||||
import ContactUsModal from '../../modules/support/frontend/js/components/contact-us-modal'
|
import ContactUsModal from '../../modules/support/frontend/js/components/contact-us-modal'
|
||||||
import { withContextRoot } from './utils/with-context-root'
|
import { ScopeDecorator } from './decorators/scope'
|
||||||
|
|
||||||
export const Generic = () => {
|
export const Generic = () => {
|
||||||
const [show, setShow] = useState(true)
|
const [show, setShow] = useState(true)
|
||||||
|
|
||||||
const handleHide = () => setShow(false)
|
const handleHide = () => setShow(false)
|
||||||
|
|
||||||
useFetchMock(fetchMock => {
|
useFetchMock(fetchMock => {
|
||||||
fetchMock.post('express:/support', { status: 200 }, { delay: 1000 })
|
fetchMock.post('/support', { status: 200 }, { delay: 1000 })
|
||||||
})
|
})
|
||||||
|
|
||||||
return withContextRoot(<ContactUsModal show={show} handleHide={handleHide} />)
|
return <ContactUsModal show={show} handleHide={handleHide} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RequestError = args => {
|
export const RequestError = args => {
|
||||||
useFetchMock(fetchMock => {
|
useFetchMock(fetchMock => {
|
||||||
fetchMock.post('express:/support', { status: 404 }, { delay: 250 })
|
fetchMock.post('/support', { status: 404 }, { delay: 250 })
|
||||||
})
|
})
|
||||||
|
|
||||||
return withContextRoot(<ContactUsModal {...args} />)
|
return <ContactUsModal {...args} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -32,4 +33,5 @@ export default {
|
||||||
argTypes: {
|
argTypes: {
|
||||||
handleHide: { action: 'close modal' },
|
handleHide: { action: 'close modal' },
|
||||||
},
|
},
|
||||||
|
decorators: [ScopeDecorator],
|
||||||
}
|
}
|
||||||
|
|
193
services/web/frontend/stories/decorators/scope.tsx
Normal file
193
services/web/frontend/stories/decorators/scope.tsx
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
import { useEffect, useMemo } from 'react'
|
||||||
|
import { get } from 'lodash'
|
||||||
|
import { ContextRoot } from '../../js/shared/context/root-context'
|
||||||
|
import { User } from '../../../types/user'
|
||||||
|
import { Project } from '../../../types/project'
|
||||||
|
import {
|
||||||
|
mockBuildFile,
|
||||||
|
mockCompile,
|
||||||
|
mockCompileError,
|
||||||
|
} from '../fixtures/compile'
|
||||||
|
import useFetchMock from '../hooks/use-fetch-mock'
|
||||||
|
|
||||||
|
const scopeWatchers = []
|
||||||
|
|
||||||
|
const initialize = () => {
|
||||||
|
const user: User = {
|
||||||
|
id: 'story-user',
|
||||||
|
email: 'story-user@example.com',
|
||||||
|
allowedFreeTrial: true,
|
||||||
|
features: { dropbox: true, symbolPalette: true },
|
||||||
|
}
|
||||||
|
|
||||||
|
const project: Project = {
|
||||||
|
_id: 'a-project',
|
||||||
|
name: 'A Project',
|
||||||
|
features: { mendeley: true, zotero: true },
|
||||||
|
tokens: {},
|
||||||
|
owner: {
|
||||||
|
_id: 'a-user',
|
||||||
|
email: 'stories@overleaf.com',
|
||||||
|
},
|
||||||
|
members: [],
|
||||||
|
invites: [],
|
||||||
|
rootDocId: '5e74f1a7ce17ae0041dfd056',
|
||||||
|
rootFolder: [
|
||||||
|
{
|
||||||
|
_id: 'root-folder-id',
|
||||||
|
name: 'rootFolder',
|
||||||
|
docs: [],
|
||||||
|
fileRefs: [],
|
||||||
|
folders: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
const scope = {
|
||||||
|
user,
|
||||||
|
project,
|
||||||
|
$watch: (key, callback) => {
|
||||||
|
scopeWatchers.push([key, callback])
|
||||||
|
},
|
||||||
|
$applyAsync: callback => {
|
||||||
|
window.setTimeout(() => {
|
||||||
|
callback()
|
||||||
|
for (const [key, watcher] of scopeWatchers) {
|
||||||
|
watcher(get(ide.$scope, key))
|
||||||
|
}
|
||||||
|
}, 0)
|
||||||
|
},
|
||||||
|
$on: (eventName, callback) => {
|
||||||
|
//
|
||||||
|
},
|
||||||
|
$broadcast: () => {},
|
||||||
|
$root: {
|
||||||
|
_references: {
|
||||||
|
keys: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ui: {
|
||||||
|
chatOpen: true,
|
||||||
|
pdfLayout: 'flat',
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
pdfViewer: 'js',
|
||||||
|
syntaxValidation: true,
|
||||||
|
},
|
||||||
|
toggleHistory: () => {},
|
||||||
|
editor: {
|
||||||
|
richText: false,
|
||||||
|
newSourceEditor: false,
|
||||||
|
sharejs_doc: {
|
||||||
|
doc_id: 'test-doc',
|
||||||
|
getSnapshot: () => 'some doc content',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hasLintingError: false,
|
||||||
|
permissionsLevel: 'owner',
|
||||||
|
}
|
||||||
|
|
||||||
|
const ide = {
|
||||||
|
$scope: scope,
|
||||||
|
socket: {
|
||||||
|
on: () => {},
|
||||||
|
removeListener: () => {},
|
||||||
|
},
|
||||||
|
fileTreeManager: {
|
||||||
|
findEntityById: () => null,
|
||||||
|
findEntityByPath: () => null,
|
||||||
|
getEntityPath: () => null,
|
||||||
|
getRootDocDirname: () => undefined,
|
||||||
|
},
|
||||||
|
editorManager: {
|
||||||
|
getCurrentDocId: () => 'foo',
|
||||||
|
openDoc: (id, options) => {
|
||||||
|
console.log('open doc', id, options)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
metadataManager: {
|
||||||
|
metadata: {
|
||||||
|
state: {
|
||||||
|
documents: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
window.user = user
|
||||||
|
|
||||||
|
window.ExposedSettings = {
|
||||||
|
appName: 'Overleaf',
|
||||||
|
maxEntitiesPerProject: 10,
|
||||||
|
maxUploadSize: 5 * 1024 * 1024,
|
||||||
|
enableSubscriptions: true,
|
||||||
|
textExtensions: [
|
||||||
|
'tex',
|
||||||
|
'latex',
|
||||||
|
'sty',
|
||||||
|
'cls',
|
||||||
|
'bst',
|
||||||
|
'bib',
|
||||||
|
'bibtex',
|
||||||
|
'txt',
|
||||||
|
'tikz',
|
||||||
|
'mtx',
|
||||||
|
'rtex',
|
||||||
|
'md',
|
||||||
|
'asy',
|
||||||
|
'latexmkrc',
|
||||||
|
'lbx',
|
||||||
|
'bbx',
|
||||||
|
'cbx',
|
||||||
|
'm',
|
||||||
|
'lco',
|
||||||
|
'dtx',
|
||||||
|
'ins',
|
||||||
|
'ist',
|
||||||
|
'def',
|
||||||
|
'clo',
|
||||||
|
'ldf',
|
||||||
|
'rmd',
|
||||||
|
'lua',
|
||||||
|
'gv',
|
||||||
|
'mf',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
window.project_id = project._id
|
||||||
|
|
||||||
|
window.metaAttributesCache = new Map()
|
||||||
|
window.metaAttributesCache.set('ol-user', user)
|
||||||
|
|
||||||
|
window.gitBridgePublicBaseUrl = 'https://git.stories.com'
|
||||||
|
|
||||||
|
window._ide = ide
|
||||||
|
|
||||||
|
return ide
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ScopeDecorator = Story => {
|
||||||
|
// mock compile on load
|
||||||
|
useFetchMock(fetchMock => {
|
||||||
|
mockCompile(fetchMock)
|
||||||
|
mockCompileError(fetchMock)
|
||||||
|
mockBuildFile(fetchMock)
|
||||||
|
})
|
||||||
|
|
||||||
|
// clear scopeWatchers on unmount
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
scopeWatchers.length = 0
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const ide = useMemo(() => {
|
||||||
|
return initialize()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContextRoot ide={ide} settings={{}}>
|
||||||
|
<Story />
|
||||||
|
</ContextRoot>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,16 +1,12 @@
|
||||||
import EditorSwitch from '../js/features/source-editor/components/editor-switch'
|
import EditorSwitch from '../js/features/source-editor/components/editor-switch'
|
||||||
import { withContextRoot } from './utils/with-context-root'
|
import { ScopeDecorator } from './decorators/scope'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Editor / Switch',
|
title: 'Editor / Switch',
|
||||||
component: EditorSwitch,
|
component: EditorSwitch,
|
||||||
|
decorators: [ScopeDecorator],
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Switcher = () => {
|
export const Switcher = () => {
|
||||||
return withContextRoot(<EditorSwitch />, {
|
return <EditorSwitch />
|
||||||
editor: {
|
|
||||||
richText: false,
|
|
||||||
newSourceEditor: false,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import MockedSocket from 'socket.io-mock'
|
import MockedSocket from 'socket.io-mock'
|
||||||
|
|
||||||
import { withContextRoot } from './utils/with-context-root'
|
|
||||||
import { rootFolderBase } from './fixtures/file-tree-base'
|
import { rootFolderBase } from './fixtures/file-tree-base'
|
||||||
import { rootFolderLimit } from './fixtures/file-tree-limit'
|
import { rootFolderLimit } from './fixtures/file-tree-limit'
|
||||||
import FileTreeRoot from '../js/features/file-tree/components/file-tree-root'
|
import FileTreeRoot from '../js/features/file-tree/components/file-tree-root'
|
||||||
import FileTreeError from '../js/features/file-tree/components/file-tree-error'
|
import FileTreeError from '../js/features/file-tree/components/file-tree-error'
|
||||||
import useFetchMock from './hooks/use-fetch-mock'
|
import useFetchMock from './hooks/use-fetch-mock'
|
||||||
|
import { ScopeDecorator } from './decorators/scope'
|
||||||
|
import { useScope } from './hooks/use-scope'
|
||||||
|
|
||||||
const MOCK_DELAY = 2000
|
const MOCK_DELAY = 2000
|
||||||
|
|
||||||
|
@ -87,24 +88,30 @@ function defaultSetupMocks(fetchMock) {
|
||||||
export const FullTree = args => {
|
export const FullTree = args => {
|
||||||
useFetchMock(defaultSetupMocks)
|
useFetchMock(defaultSetupMocks)
|
||||||
|
|
||||||
return withContextRoot(<FileTreeRoot {...args} />, {
|
useScope({
|
||||||
project: DEFAULT_PROJECT,
|
project: DEFAULT_PROJECT,
|
||||||
permissionsLevel: 'owner',
|
permissionsLevel: 'owner',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return <FileTreeRoot {...args} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ReadOnly = args => {
|
export const ReadOnly = args => {
|
||||||
return withContextRoot(<FileTreeRoot {...args} />, {
|
useScope({
|
||||||
project: DEFAULT_PROJECT,
|
project: DEFAULT_PROJECT,
|
||||||
permissionsLevel: 'readOnly',
|
permissionsLevel: 'readOnly',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return <FileTreeRoot {...args} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Disconnected = args => {
|
export const Disconnected = args => {
|
||||||
return withContextRoot(<FileTreeRoot {...args} />, {
|
useScope({
|
||||||
project: DEFAULT_PROJECT,
|
project: DEFAULT_PROJECT,
|
||||||
permissionsLevel: 'owner',
|
permissionsLevel: 'owner',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return <FileTreeRoot {...args} />
|
||||||
}
|
}
|
||||||
Disconnected.args = { isConnected: false }
|
Disconnected.args = { isConnected: false }
|
||||||
|
|
||||||
|
@ -125,25 +132,34 @@ export const NetworkErrors = args => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return withContextRoot(<FileTreeRoot {...args} />, {
|
useScope({
|
||||||
project: DEFAULT_PROJECT,
|
project: DEFAULT_PROJECT,
|
||||||
permissionsLevel: 'owner',
|
permissionsLevel: 'owner',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return <FileTreeRoot {...args} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FallbackError = args => {
|
export const FallbackError = args => {
|
||||||
return withContextRoot(<FileTreeError {...args} />, {
|
useScope({
|
||||||
project: DEFAULT_PROJECT,
|
project: DEFAULT_PROJECT,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return <FileTreeError {...args} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FilesLimit = args => {
|
export const FilesLimit = args => {
|
||||||
useFetchMock(defaultSetupMocks)
|
useFetchMock(defaultSetupMocks)
|
||||||
|
|
||||||
return withContextRoot(<FileTreeRoot {...args} />, {
|
useScope({
|
||||||
project: { ...DEFAULT_PROJECT, rootFolder: rootFolderLimit },
|
project: {
|
||||||
|
...DEFAULT_PROJECT,
|
||||||
|
rootFolder: rootFolderLimit,
|
||||||
|
},
|
||||||
permissionsLevel: 'owner',
|
permissionsLevel: 'owner',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return <FileTreeRoot {...args} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -167,6 +183,7 @@ export default {
|
||||||
onSelect: { action: 'onSelect' },
|
onSelect: { action: 'onSelect' },
|
||||||
},
|
},
|
||||||
decorators: [
|
decorators: [
|
||||||
|
ScopeDecorator,
|
||||||
Story => (
|
Story => (
|
||||||
<>
|
<>
|
||||||
<style>{'html, body, .file-tree { height: 100%; width: 100%; }'}</style>
|
<style>{'html, body, .file-tree { height: 100%; width: 100%; }'}</style>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { ContextRoot } from '../js/shared/context/root-context'
|
|
||||||
import FileView from '../js/features/file-view/components/file-view'
|
import FileView from '../js/features/file-view/components/file-view'
|
||||||
import useFetchMock from './hooks/use-fetch-mock'
|
import useFetchMock from './hooks/use-fetch-mock'
|
||||||
|
import { ScopeDecorator } from './decorators/scope'
|
||||||
|
|
||||||
const bodies = {
|
const bodies = {
|
||||||
latex: `\\documentclass{article}
|
latex: `\\documentclass{article}
|
||||||
|
@ -252,11 +252,5 @@ export default {
|
||||||
argTypes: {
|
argTypes: {
|
||||||
storeReferencesKeys: { action: 'store references keys' },
|
storeReferencesKeys: { action: 'store references keys' },
|
||||||
},
|
},
|
||||||
decorators: [
|
decorators: [ScopeDecorator],
|
||||||
Story => (
|
|
||||||
<ContextRoot ide={window._ide} settings={{}}>
|
|
||||||
<Story />
|
|
||||||
</ContextRoot>
|
|
||||||
),
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
|
34
services/web/frontend/stories/fixtures/chat-messages.js
Normal file
34
services/web/frontend/stories/fixtures/chat-messages.js
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import { v4 as uuid } from 'uuid'
|
||||||
|
|
||||||
|
const ONE_MINUTE = 60 * 1000
|
||||||
|
|
||||||
|
const user = {
|
||||||
|
id: 'fake_user',
|
||||||
|
first_name: 'mortimer',
|
||||||
|
email: 'fake@example.com',
|
||||||
|
}
|
||||||
|
|
||||||
|
const user2 = {
|
||||||
|
id: 'another_fake_user',
|
||||||
|
first_name: 'leopold',
|
||||||
|
email: 'another_fake@example.com',
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateMessages(count) {
|
||||||
|
const messages = []
|
||||||
|
let timestamp = new Date().getTime() // newest message goes first
|
||||||
|
for (let i = 0; i <= count; i++) {
|
||||||
|
const author = Math.random() > 0.5 ? user : user2
|
||||||
|
// modify the timestamp so the previous message has 70% chances to be within 5 minutes from
|
||||||
|
// the current one, for grouping purposes
|
||||||
|
timestamp -= (4.3 + Math.random()) * ONE_MINUTE
|
||||||
|
|
||||||
|
messages.push({
|
||||||
|
id: uuid(),
|
||||||
|
content: `message #${i}`,
|
||||||
|
user: author,
|
||||||
|
timestamp,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return messages
|
||||||
|
}
|
|
@ -58,7 +58,7 @@ export const mockCompile = (fetchMock, delay = 1000) =>
|
||||||
outputFiles: cloneDeep(outputFiles),
|
outputFiles: cloneDeep(outputFiles),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ delay }
|
{ delay, overwriteRoutes: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
export const mockCompileError = (fetchMock, status = 'success', delay = 1000) =>
|
export const mockCompileError = (fetchMock, status = 'success', delay = 1000) =>
|
||||||
|
@ -97,6 +97,7 @@ export const mockCompileValidationIssues = (
|
||||||
export const mockClearCache = fetchMock =>
|
export const mockClearCache = fetchMock =>
|
||||||
fetchMock.delete('express:/project/:projectId/output', 204, {
|
fetchMock.delete('express:/project/:projectId/output', 204, {
|
||||||
delay: 1000,
|
delay: 1000,
|
||||||
|
overwriteRoutes: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const mockBuildFile = fetchMock =>
|
export const mockBuildFile = fetchMock =>
|
||||||
|
@ -156,7 +157,7 @@ LaTeX Font Info: External font \`cmex10' loaded for size
|
||||||
return 404
|
return 404
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ sendAsJson: false }
|
{ sendAsJson: false, overwriteRoutes: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
const mockHighlights = [
|
const mockHighlights = [
|
||||||
|
@ -215,7 +216,7 @@ export const mockValidPdf = fetchMock =>
|
||||||
xhr.send()
|
xhr.send()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
{ sendAsJson: false }
|
{ sendAsJson: false, overwriteRoutes: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
export const mockSynctex = fetchMock =>
|
export const mockSynctex = fetchMock =>
|
||||||
|
|
41
services/web/frontend/stories/fixtures/contacts.js
Normal file
41
services/web/frontend/stories/fixtures/contacts.js
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
export const contacts = [
|
||||||
|
// user with edited name
|
||||||
|
{
|
||||||
|
type: 'user',
|
||||||
|
email: 'test-user@example.com',
|
||||||
|
first_name: 'Test',
|
||||||
|
last_name: 'User',
|
||||||
|
name: 'Test User',
|
||||||
|
},
|
||||||
|
// user with default name (email prefix)
|
||||||
|
{
|
||||||
|
type: 'user',
|
||||||
|
email: 'test@example.com',
|
||||||
|
first_name: 'test',
|
||||||
|
},
|
||||||
|
// no last name
|
||||||
|
{
|
||||||
|
type: 'user',
|
||||||
|
first_name: 'Eratosthenes',
|
||||||
|
email: 'eratosthenes@example.com',
|
||||||
|
},
|
||||||
|
// more users
|
||||||
|
{
|
||||||
|
type: 'user',
|
||||||
|
first_name: 'Claudius',
|
||||||
|
last_name: 'Ptolemy',
|
||||||
|
email: 'ptolemy@example.com',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'user',
|
||||||
|
first_name: 'Abd al-Rahman',
|
||||||
|
last_name: 'Al-Sufi',
|
||||||
|
email: 'al-sufi@example.com',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'user',
|
||||||
|
first_name: 'Nicolaus',
|
||||||
|
last_name: 'Copernicus',
|
||||||
|
email: 'copernicus@example.com',
|
||||||
|
},
|
||||||
|
]
|
|
@ -1,71 +0,0 @@
|
||||||
import sinon from 'sinon'
|
|
||||||
|
|
||||||
export function setupContext() {
|
|
||||||
window.project_id = '1234'
|
|
||||||
window.user = {
|
|
||||||
id: 'fake_user',
|
|
||||||
allowedFreeTrial: true,
|
|
||||||
}
|
|
||||||
const $scope = {
|
|
||||||
...window._ide?.$scope,
|
|
||||||
user: window.user,
|
|
||||||
project: {
|
|
||||||
_id: window.project_id,
|
|
||||||
name: 'Project Fake Name',
|
|
||||||
features: {},
|
|
||||||
rootFolder: [
|
|
||||||
{
|
|
||||||
_id: 'root-folder-id',
|
|
||||||
name: 'rootFolder',
|
|
||||||
docs: [],
|
|
||||||
folders: [],
|
|
||||||
fileRefs: [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
$watch: () => {},
|
|
||||||
$applyAsync: () => {},
|
|
||||||
$broadcast: () => {},
|
|
||||||
ui: {
|
|
||||||
chatOpen: true,
|
|
||||||
pdfLayout: 'flat',
|
|
||||||
},
|
|
||||||
settings: {
|
|
||||||
pdfViewer: 'js',
|
|
||||||
},
|
|
||||||
toggleHistory: () => {},
|
|
||||||
}
|
|
||||||
window._ide = {
|
|
||||||
...window._ide,
|
|
||||||
$scope,
|
|
||||||
socket: {
|
|
||||||
on: sinon.stub(),
|
|
||||||
removeListener: sinon.stub(),
|
|
||||||
},
|
|
||||||
fileTreeManager: {
|
|
||||||
findEntityById: () => null,
|
|
||||||
findEntityByPath: () => null,
|
|
||||||
getEntityPath: () => null,
|
|
||||||
getRootDocDirname: () => undefined,
|
|
||||||
},
|
|
||||||
editorManager: {
|
|
||||||
getCurrentDocId: () => 'foo',
|
|
||||||
openDoc: (id, options) => {
|
|
||||||
console.log('open doc', id, options)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
metadataManager: {
|
|
||||||
metadata: {
|
|
||||||
state: {
|
|
||||||
documents: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
window.ExposedSettings = window.ExposedSettings || {}
|
|
||||||
window.ExposedSettings.appName = 'Overleaf'
|
|
||||||
window.gitBridgePublicBaseUrl = 'https://git.stories.com'
|
|
||||||
|
|
||||||
window.metaAttributesCache = window.metaAttributesCache || new Map()
|
|
||||||
window.metaAttributesCache.set('ol-user', window.user)
|
|
||||||
}
|
|
48
services/web/frontend/stories/fixtures/project.ts
Normal file
48
services/web/frontend/stories/fixtures/project.ts
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
import { Project } from '../../../types/project'
|
||||||
|
|
||||||
|
export const project: Project = {
|
||||||
|
_id: 'a-project',
|
||||||
|
name: 'A Project',
|
||||||
|
features: {
|
||||||
|
collaborators: -1, // unlimited
|
||||||
|
},
|
||||||
|
publicAccesLevel: 'private',
|
||||||
|
tokens: {
|
||||||
|
readOnly: 'ro-token',
|
||||||
|
readAndWrite: 'rw-token',
|
||||||
|
},
|
||||||
|
owner: {
|
||||||
|
_id: 'project-owner',
|
||||||
|
email: 'stories@overleaf.com',
|
||||||
|
},
|
||||||
|
members: [
|
||||||
|
{
|
||||||
|
_id: 'viewer-member',
|
||||||
|
type: 'user',
|
||||||
|
privileges: 'readOnly',
|
||||||
|
name: 'Viewer User',
|
||||||
|
email: 'viewer@example.com',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: 'author-member',
|
||||||
|
type: 'user',
|
||||||
|
privileges: 'readAndWrite',
|
||||||
|
name: 'Author User',
|
||||||
|
email: 'author@example.com',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
invites: [
|
||||||
|
{
|
||||||
|
_id: 'test-invite-1',
|
||||||
|
privileges: 'readOnly',
|
||||||
|
name: 'Invited Viewer',
|
||||||
|
email: 'invited-viewer@example.com',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: 'test-invite-2',
|
||||||
|
privileges: 'readAndWrite',
|
||||||
|
name: 'Invited Author',
|
||||||
|
email: 'invited-author@example.com',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
import { ContextRoot } from '../js/shared/context/root-context'
|
|
||||||
import importOverleafModules from '../macros/import-overleaf-module.macro'
|
import importOverleafModules from '../macros/import-overleaf-module.macro'
|
||||||
import useFetchMock from './hooks/use-fetch-mock'
|
import { ScopeDecorator } from './decorators/scope'
|
||||||
|
|
||||||
const [
|
const [
|
||||||
{
|
{
|
||||||
|
@ -19,7 +18,10 @@ CollaboratorModal.args = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TeaserModal = args => {
|
export const TeaserModal = args => {
|
||||||
useFetchMock(fetchMock => fetchMock.post('express:/event/:key', 202))
|
// TODO: mock navigator.sendBeacon?
|
||||||
|
// useFetchMock(fetchMock => {
|
||||||
|
// fetchMock.post('express:/event/:key', 202)
|
||||||
|
// })
|
||||||
|
|
||||||
return <GitBridgeModal {...args} />
|
return <GitBridgeModal {...args} />
|
||||||
}
|
}
|
||||||
|
@ -36,13 +38,5 @@ export default {
|
||||||
argTypes: {
|
argTypes: {
|
||||||
handleHide: { action: 'handleHide' },
|
handleHide: { action: 'handleHide' },
|
||||||
},
|
},
|
||||||
decorators: [
|
decorators: [ScopeDecorator],
|
||||||
Story => (
|
|
||||||
<>
|
|
||||||
<ContextRoot ide={window._ide} settings={{}}>
|
|
||||||
<Story />
|
|
||||||
</ContextRoot>
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
import { useEffect } from 'react'
|
|
||||||
import fetchMock from 'fetch-mock'
|
|
||||||
fetchMock.config.fallbackToNetwork = true
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Run callback to mock fetch routes, call restore() when unmounted
|
|
||||||
*/
|
|
||||||
export default function useFetchMock(callback) {
|
|
||||||
useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
fetchMock.restore()
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
// Running fetchMock.restore() here as well,
|
|
||||||
// in case there was an error before the component was unmounted.
|
|
||||||
fetchMock.restore()
|
|
||||||
|
|
||||||
// The callback has to be run here, rather than in useEffect,
|
|
||||||
// so it's run before the component is rendered.
|
|
||||||
callback(fetchMock)
|
|
||||||
}
|
|
16
services/web/frontend/stories/hooks/use-fetch-mock.tsx
Normal file
16
services/web/frontend/stories/hooks/use-fetch-mock.tsx
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import { useLayoutEffect } from 'react'
|
||||||
|
import fetchMock from 'fetch-mock'
|
||||||
|
fetchMock.config.fallbackToNetwork = true
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run callback to mock fetch routes, call restore() when unmounted
|
||||||
|
*/
|
||||||
|
export default function useFetchMock(callback) {
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
callback(fetchMock)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
fetchMock.restore()
|
||||||
|
}
|
||||||
|
}, [callback])
|
||||||
|
}
|
8
services/web/frontend/stories/hooks/use-meta.tsx
Normal file
8
services/web/frontend/stories/hooks/use-meta.tsx
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
/**
|
||||||
|
* Set values on window.metaAttributesCache, for use in Storybook stories
|
||||||
|
*/
|
||||||
|
export const useMeta = (meta: Record<string, unknown>) => {
|
||||||
|
for (const [key, value] of Object.entries(meta)) {
|
||||||
|
window.metaAttributesCache.set(key, value)
|
||||||
|
}
|
||||||
|
}
|
16
services/web/frontend/stories/hooks/use-scope.tsx
Normal file
16
services/web/frontend/stories/hooks/use-scope.tsx
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import { merge } from 'lodash'
|
||||||
|
import { useLayoutEffect, useRef } from 'react'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge properties with the scope object, for use in Storybook stories
|
||||||
|
*/
|
||||||
|
export const useScope = (scope: Record<string, unknown>) => {
|
||||||
|
const scopeRef = useRef(null)
|
||||||
|
if (scopeRef.current === null) {
|
||||||
|
scopeRef.current = scope
|
||||||
|
}
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
merge(window._ide.$scope, scopeRef.current)
|
||||||
|
}, [])
|
||||||
|
}
|
|
@ -1,27 +1,10 @@
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { withContextRoot } from './../../utils/with-context-root'
|
|
||||||
import FileTreeContext from '../../../js/features/file-tree/components/file-tree-context'
|
import FileTreeContext from '../../../js/features/file-tree/components/file-tree-context'
|
||||||
import FileTreeCreateNameProvider from '../../../js/features/file-tree/contexts/file-tree-create-name'
|
import FileTreeCreateNameProvider from '../../../js/features/file-tree/contexts/file-tree-create-name'
|
||||||
import FileTreeCreateFormProvider from '../../../js/features/file-tree/contexts/file-tree-create-form'
|
import FileTreeCreateFormProvider from '../../../js/features/file-tree/contexts/file-tree-create-form'
|
||||||
import { useFileTreeActionable } from '../../../js/features/file-tree/contexts/file-tree-actionable'
|
import { useFileTreeActionable } from '../../../js/features/file-tree/contexts/file-tree-actionable'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
export const DEFAULT_PROJECT = {
|
|
||||||
_id: '123abc',
|
|
||||||
name: 'Some Project',
|
|
||||||
rootDocId: '5e74f1a7ce17ae0041dfd056',
|
|
||||||
rootFolder: [
|
|
||||||
{
|
|
||||||
_id: 'root-folder-id',
|
|
||||||
name: 'rootFolder',
|
|
||||||
docs: [],
|
|
||||||
folders: [],
|
|
||||||
fileRefs: [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
features: { mendeley: true, zotero: true },
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultFileTreeContextProps = {
|
const defaultFileTreeContextProps = {
|
||||||
refProviders: { mendeley: false, zotero: false },
|
refProviders: { mendeley: false, zotero: false },
|
||||||
reindexReferences: () => {
|
reindexReferences: () => {
|
||||||
|
@ -87,19 +70,6 @@ export const mockCreateFileModalFetch = fetchMock =>
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
.post('express:/project/:projectId/compile', {
|
|
||||||
status: 'success',
|
|
||||||
outputFiles: [
|
|
||||||
{
|
|
||||||
build: 'foo',
|
|
||||||
path: 'baz.jpg',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
build: 'foo',
|
|
||||||
path: 'ball.jpg',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
.post('express:/project/:projectId/doc', (path, req) => {
|
.post('express:/project/:projectId/doc', (path, req) => {
|
||||||
console.log({ path, req })
|
console.log({ path, req })
|
||||||
return 204
|
return 204
|
||||||
|
@ -114,14 +84,10 @@ export const mockCreateFileModalFetch = fetchMock =>
|
||||||
})
|
})
|
||||||
|
|
||||||
export const createFileModalDecorator =
|
export const createFileModalDecorator =
|
||||||
(
|
(fileTreeContextProps = {}, createMode = 'doc') =>
|
||||||
fileTreeContextProps = {},
|
// eslint-disable-next-line react/display-name
|
||||||
projectProps = {},
|
|
||||||
createMode = 'doc'
|
|
||||||
// eslint-disable-next-line react/display-name
|
|
||||||
) =>
|
|
||||||
Story => {
|
Story => {
|
||||||
return withContextRoot(
|
return (
|
||||||
<FileTreeContext
|
<FileTreeContext
|
||||||
{...defaultFileTreeContextProps}
|
{...defaultFileTreeContextProps}
|
||||||
{...fileTreeContextProps}
|
{...fileTreeContextProps}
|
||||||
|
@ -133,11 +99,7 @@ export const createFileModalDecorator =
|
||||||
</OpenCreateFileModal>
|
</OpenCreateFileModal>
|
||||||
</FileTreeCreateFormProvider>
|
</FileTreeCreateFormProvider>
|
||||||
</FileTreeCreateNameProvider>
|
</FileTreeCreateNameProvider>
|
||||||
</FileTreeContext>,
|
</FileTreeContext>
|
||||||
{
|
|
||||||
project: { ...DEFAULT_PROJECT, ...projectProps },
|
|
||||||
permissionsLevel: 'owner',
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,8 @@ import {
|
||||||
} from './create-file-modal-decorator'
|
} from './create-file-modal-decorator'
|
||||||
import FileTreeModalCreateFile from '../../../js/features/file-tree/components/modals/file-tree-modal-create-file'
|
import FileTreeModalCreateFile from '../../../js/features/file-tree/components/modals/file-tree-modal-create-file'
|
||||||
import useFetchMock from '../../hooks/use-fetch-mock'
|
import useFetchMock from '../../hooks/use-fetch-mock'
|
||||||
|
import { ScopeDecorator } from '../../decorators/scope'
|
||||||
|
import { useScope } from '../../hooks/use-scope'
|
||||||
|
|
||||||
export const MinimalFeatures = args => {
|
export const MinimalFeatures = args => {
|
||||||
useFetchMock(mockCreateFileModalFetch)
|
useFetchMock(mockCreateFileModalFetch)
|
||||||
|
@ -75,28 +77,26 @@ ErrorImportingFileFromReferenceProvider.decorators = [
|
||||||
export const FileLimitReached = args => {
|
export const FileLimitReached = args => {
|
||||||
useFetchMock(mockCreateFileModalFetch)
|
useFetchMock(mockCreateFileModalFetch)
|
||||||
|
|
||||||
|
useScope({
|
||||||
|
project: {
|
||||||
|
rootFolder: {
|
||||||
|
_id: 'root-folder-id',
|
||||||
|
name: 'rootFolder',
|
||||||
|
docs: Array.from({ length: 10 }, (_, index) => ({
|
||||||
|
_id: `entity-${index}`,
|
||||||
|
})),
|
||||||
|
fileRefs: [],
|
||||||
|
folders: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
return <FileTreeModalCreateFile {...args} />
|
return <FileTreeModalCreateFile {...args} />
|
||||||
}
|
}
|
||||||
FileLimitReached.decorators = [
|
FileLimitReached.decorators = [createFileModalDecorator()]
|
||||||
createFileModalDecorator(
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
rootFolder: [
|
|
||||||
{
|
|
||||||
_id: 'root-folder-id',
|
|
||||||
name: 'rootFolder',
|
|
||||||
docs: Array.from({ length: 10 }, (_, index) => ({
|
|
||||||
_id: `entity-${index}`,
|
|
||||||
})),
|
|
||||||
fileRefs: [],
|
|
||||||
folders: [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Editor / Modals / Create File',
|
title: 'Editor / Modals / Create File',
|
||||||
component: FileTreeModalCreateFile,
|
component: FileTreeModalCreateFile,
|
||||||
|
decorators: [ScopeDecorator],
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import ErrorMessage from '../../../js/features/file-tree/components/file-tree-create/error-message'
|
import ErrorMessage from '../../../js/features/file-tree/components/file-tree-create/error-message'
|
||||||
import { createFileModalDecorator } from './create-file-modal-decorator'
|
|
||||||
import { FetchError } from '../../../js/infrastructure/fetch-json'
|
import { FetchError } from '../../../js/infrastructure/fetch-json'
|
||||||
import {
|
import {
|
||||||
BlockedFilenameError,
|
BlockedFilenameError,
|
||||||
|
@ -19,7 +18,6 @@ export const KeyedErrors = () => {
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
KeyedErrors.decorators = [createFileModalDecorator()]
|
|
||||||
|
|
||||||
export const FetchStatusErrors = () => {
|
export const FetchStatusErrors = () => {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import OutlinePane from '../js/features/outline/components/outline-pane'
|
import OutlinePane from '../js/features/outline/components/outline-pane'
|
||||||
import { ContextRoot } from '../js/shared/context/root-context'
|
import { ScopeDecorator } from './decorators/scope'
|
||||||
|
|
||||||
export const Basic = args => <OutlinePane {...args} />
|
export const Basic = args => <OutlinePane {...args} />
|
||||||
Basic.args = {
|
Basic.args = {
|
||||||
|
@ -52,11 +52,5 @@ export default {
|
||||||
jumpToLine: () => {},
|
jumpToLine: () => {},
|
||||||
onToggle: () => {},
|
onToggle: () => {},
|
||||||
},
|
},
|
||||||
decorators: [
|
decorators: [ScopeDecorator],
|
||||||
Story => (
|
|
||||||
<ContextRoot ide={window._ide} settings={{}}>
|
|
||||||
<Story />
|
|
||||||
</ContextRoot>
|
|
||||||
),
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,20 @@
|
||||||
import ErrorBoundaryFallback from '../js/features/pdf-preview/components/error-boundary-fallback'
|
import ErrorBoundaryFallback from '../js/features/pdf-preview/components/error-boundary-fallback'
|
||||||
import { withContextRoot } from './utils/with-context-root'
|
import { ScopeDecorator } from './decorators/scope'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Editor / PDF Preview / Error Boundary',
|
title: 'Editor / PDF Preview / Error Boundary',
|
||||||
component: ErrorBoundaryFallback,
|
component: ErrorBoundaryFallback,
|
||||||
|
decorators: [ScopeDecorator],
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PreviewErrorBoundary = () => {
|
export const PreviewErrorBoundary = () => {
|
||||||
return withContextRoot(<ErrorBoundaryFallback type="preview" />)
|
return <ErrorBoundaryFallback type="preview" />
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PdfErrorBoundary = () => {
|
export const PdfErrorBoundary = () => {
|
||||||
return withContextRoot(<ErrorBoundaryFallback type="pdf" />)
|
return <ErrorBoundaryFallback type="pdf" />
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LogsErrorBoundary = () => {
|
export const LogsErrorBoundary = () => {
|
||||||
return withContextRoot(<ErrorBoundaryFallback type="logs" />)
|
return <ErrorBoundaryFallback type="logs" />
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { withContextRoot } from './utils/with-context-root'
|
|
||||||
import { useCallback, useMemo, useState } from 'react'
|
import { useCallback, useMemo, useState } from 'react'
|
||||||
import useFetchMock from './hooks/use-fetch-mock'
|
import useFetchMock from './hooks/use-fetch-mock'
|
||||||
import { Button } from 'react-bootstrap'
|
import { Button } from 'react-bootstrap'
|
||||||
|
@ -21,6 +20,7 @@ import {
|
||||||
outputFiles,
|
outputFiles,
|
||||||
} from './fixtures/compile'
|
} from './fixtures/compile'
|
||||||
import { cloneDeep } from 'lodash'
|
import { cloneDeep } from 'lodash'
|
||||||
|
import { ScopeDecorator } from './decorators/scope'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Editor / PDF Preview',
|
title: 'Editor / PDF Preview',
|
||||||
|
@ -30,34 +30,7 @@ export default {
|
||||||
PdfFileList,
|
PdfFileList,
|
||||||
PdfPreviewError,
|
PdfPreviewError,
|
||||||
},
|
},
|
||||||
}
|
decorators: [ScopeDecorator],
|
||||||
|
|
||||||
const project = {
|
|
||||||
_id: 'a-project',
|
|
||||||
name: 'A Project',
|
|
||||||
features: {},
|
|
||||||
tokens: {},
|
|
||||||
owner: {
|
|
||||||
_id: 'a-user',
|
|
||||||
email: 'stories@overleaf.com',
|
|
||||||
},
|
|
||||||
members: [],
|
|
||||||
invites: [],
|
|
||||||
}
|
|
||||||
|
|
||||||
const scope = {
|
|
||||||
project,
|
|
||||||
settings: {
|
|
||||||
syntaxValidation: true,
|
|
||||||
},
|
|
||||||
hasLintingError: false,
|
|
||||||
$applyAsync: () => {},
|
|
||||||
editor: {
|
|
||||||
sharejs_doc: {
|
|
||||||
doc_id: 'test-doc',
|
|
||||||
getSnapshot: () => 'some doc content',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Interactive = () => {
|
export const Interactive = () => {
|
||||||
|
@ -205,12 +178,11 @@ export const Interactive = () => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return withContextRoot(
|
return (
|
||||||
<div className="pdf-viewer">
|
<div className="pdf-viewer">
|
||||||
<PdfPreviewPane />
|
<PdfPreviewPane />
|
||||||
<Inner />
|
<Inner />
|
||||||
</div>,
|
</div>
|
||||||
scope
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,12 +248,11 @@ export const CompileError = () => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return withContextRoot(
|
return (
|
||||||
<>
|
<>
|
||||||
<PdfPreviewPane />
|
<PdfPreviewPane />
|
||||||
<Inner />
|
<Inner />
|
||||||
</>,
|
</>
|
||||||
scope
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -309,7 +280,7 @@ export const DisplayError = () => {
|
||||||
mockCompile(fetchMock)
|
mockCompile(fetchMock)
|
||||||
})
|
})
|
||||||
|
|
||||||
return withContextRoot(
|
return (
|
||||||
<>
|
<>
|
||||||
{compileErrors.map(error => (
|
{compileErrors.map(error => (
|
||||||
<div
|
<div
|
||||||
|
@ -320,8 +291,7 @@ export const DisplayError = () => {
|
||||||
<PdfPreviewError error={error} />
|
<PdfPreviewError error={error} />
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</>,
|
</>
|
||||||
scope
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -332,11 +302,10 @@ export const HybridToolbar = () => {
|
||||||
mockEventTracking(fetchMock)
|
mockEventTracking(fetchMock)
|
||||||
})
|
})
|
||||||
|
|
||||||
return withContextRoot(
|
return (
|
||||||
<div className="pdf">
|
<div className="pdf">
|
||||||
<PdfPreviewHybridToolbar />
|
<PdfPreviewHybridToolbar />
|
||||||
</div>,
|
</div>
|
||||||
scope
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -361,11 +330,10 @@ export const Logs = () => {
|
||||||
mockClearCache(fetchMock)
|
mockClearCache(fetchMock)
|
||||||
})
|
})
|
||||||
|
|
||||||
return withContextRoot(
|
return (
|
||||||
<div className="pdf">
|
<div className="pdf">
|
||||||
<PdfLogsViewer />
|
<PdfLogsViewer />
|
||||||
</div>,
|
</div>
|
||||||
scope
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -393,5 +361,5 @@ export const ValidationIssues = () => {
|
||||||
mockBuildFile(fetchMock)
|
mockBuildFile(fetchMock)
|
||||||
})
|
})
|
||||||
|
|
||||||
return withContextRoot(<PdfPreviewPane />, scope)
|
return <PdfPreviewPane />
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
import { useEffect, Suspense } from 'react'
|
|
||||||
import useFetchMock from './hooks/use-fetch-mock'
|
import useFetchMock from './hooks/use-fetch-mock'
|
||||||
import { withContextRoot } from './utils/with-context-root'
|
|
||||||
import PdfSynctexControls from '../js/features/pdf-preview/components/pdf-synctex-controls'
|
import PdfSynctexControls from '../js/features/pdf-preview/components/pdf-synctex-controls'
|
||||||
import PdfViewer from '../js/features/pdf-preview/components/pdf-viewer'
|
import PdfViewer from '../js/features/pdf-preview/components/pdf-viewer'
|
||||||
import {
|
import {
|
||||||
|
@ -9,24 +7,13 @@ import {
|
||||||
mockSynctex,
|
mockSynctex,
|
||||||
mockValidPdf,
|
mockValidPdf,
|
||||||
} from './fixtures/compile'
|
} from './fixtures/compile'
|
||||||
|
import { useEffect, Suspense } from 'react'
|
||||||
|
import { ScopeDecorator } from './decorators/scope'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Editor / PDF Viewer',
|
title: 'Editor / PDF Viewer',
|
||||||
component: PdfViewer,
|
component: PdfViewer,
|
||||||
}
|
decorators: [ScopeDecorator],
|
||||||
|
|
||||||
const project = {
|
|
||||||
_id: 'story-project',
|
|
||||||
}
|
|
||||||
|
|
||||||
const scope = {
|
|
||||||
project,
|
|
||||||
editor: {
|
|
||||||
sharejs_doc: {
|
|
||||||
doc_id: 'test-doc',
|
|
||||||
getSnapshot: () => 'some doc content',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Interactive = () => {
|
export const Interactive = () => {
|
||||||
|
@ -45,17 +32,16 @@ export const Interactive = () => {
|
||||||
)
|
)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return withContextRoot(
|
return (
|
||||||
<Suspense fallback="Loading">
|
<div>
|
||||||
<div>
|
<div className="pdf-viewer">
|
||||||
<div className="pdf-viewer">
|
<Suspense fallback={null}>
|
||||||
<PdfViewer />
|
<PdfViewer />
|
||||||
</div>
|
</Suspense>
|
||||||
<div style={{ position: 'absolute', top: 150, left: 50 }}>
|
|
||||||
<PdfSynctexControls />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Suspense>,
|
<div style={{ position: 'absolute', top: 150, left: 50 }}>
|
||||||
scope
|
<PdfSynctexControls />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,9 @@ import LinkingSection from '../../js/features/settings/components/linking-sectio
|
||||||
import { setDefaultMeta, defaultSetupMocks } from './helpers/linking'
|
import { setDefaultMeta, defaultSetupMocks } from './helpers/linking'
|
||||||
import { UserProvider } from '../../js/shared/context/user-context'
|
import { UserProvider } from '../../js/shared/context/user-context'
|
||||||
import { SSOProvider } from '../../js/features/settings/context/sso-context'
|
import { SSOProvider } from '../../js/features/settings/context/sso-context'
|
||||||
|
import { ScopeDecorator } from '../decorators/scope'
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
import { useMeta } from '../hooks/use-meta'
|
||||||
|
|
||||||
const MOCK_DELAY = 1000
|
const MOCK_DELAY = 1000
|
||||||
|
|
||||||
|
@ -21,17 +24,23 @@ export const Section = args => {
|
||||||
|
|
||||||
export const SectionAllUnlinked = args => {
|
export const SectionAllUnlinked = args => {
|
||||||
useFetchMock(defaultSetupMocks)
|
useFetchMock(defaultSetupMocks)
|
||||||
setDefaultMeta()
|
|
||||||
window.metaAttributesCache.set('ol-thirdPartyIds', {})
|
useMeta({
|
||||||
window.metaAttributesCache.set('ol-user', {
|
'ol-thirdPartyIds': {},
|
||||||
features: { github: true, dropbox: true, mendeley: true, zotero: true },
|
'ol-user': {
|
||||||
refProviders: {
|
features: { github: true, dropbox: true, mendeley: true, zotero: true },
|
||||||
mendeley: false,
|
refProviders: {
|
||||||
zotero: false,
|
mendeley: false,
|
||||||
|
zotero: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
'ol-github': { enabled: false },
|
||||||
|
'ol-dropbox': { registered: false },
|
||||||
})
|
})
|
||||||
window.metaAttributesCache.set('ol-github', { enabled: false })
|
|
||||||
window.metaAttributesCache.set('ol-dropbox', { registered: false })
|
useEffect(() => {
|
||||||
|
setDefaultMeta()
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UserProvider>
|
<UserProvider>
|
||||||
|
@ -84,4 +93,5 @@ export const SectionProjetSyncSuccess = args => {
|
||||||
export default {
|
export default {
|
||||||
title: 'Account Settings / Linking',
|
title: 'Account Settings / Linking',
|
||||||
component: LinkingSection,
|
component: LinkingSection,
|
||||||
|
decorators: [ScopeDecorator],
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,62 +1,71 @@
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import ShareProjectModal from '../js/features/share-project-modal/components/share-project-modal'
|
import ShareProjectModal from '../js/features/share-project-modal/components/share-project-modal'
|
||||||
import useFetchMock from './hooks/use-fetch-mock'
|
import useFetchMock from './hooks/use-fetch-mock'
|
||||||
import { withContextRoot } from './utils/with-context-root'
|
import { useScope } from './hooks/use-scope'
|
||||||
|
import { ScopeDecorator } from './decorators/scope'
|
||||||
|
import { contacts } from './fixtures/contacts'
|
||||||
|
import { project } from './fixtures/project'
|
||||||
|
|
||||||
export const LinkSharingOff = args => {
|
export const LinkSharingOff = args => {
|
||||||
useFetchMock(setupFetchMock)
|
useFetchMock(setupFetchMock)
|
||||||
|
|
||||||
const project = {
|
useScope({
|
||||||
...args.project,
|
project: {
|
||||||
publicAccesLevel: 'private',
|
...args.project,
|
||||||
}
|
publicAccesLevel: 'private',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
return withContextRoot(<ShareProjectModal {...args} />, { project })
|
return <ShareProjectModal {...args} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LinkSharingOn = args => {
|
export const LinkSharingOn = args => {
|
||||||
useFetchMock(setupFetchMock)
|
useFetchMock(setupFetchMock)
|
||||||
|
|
||||||
const project = {
|
useScope({
|
||||||
...args.project,
|
project: {
|
||||||
publicAccesLevel: 'tokenBased',
|
...args.project,
|
||||||
}
|
publicAccesLevel: 'tokenBased',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
return withContextRoot(<ShareProjectModal {...args} />, { project })
|
return <ShareProjectModal {...args} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LinkSharingLoading = args => {
|
export const LinkSharingLoading = args => {
|
||||||
useFetchMock(setupFetchMock)
|
useFetchMock(setupFetchMock)
|
||||||
|
|
||||||
const project = {
|
useScope({
|
||||||
...args.project,
|
project: {
|
||||||
publicAccesLevel: 'tokenBased',
|
...args.project,
|
||||||
tokens: undefined,
|
publicAccesLevel: 'tokenBased',
|
||||||
}
|
tokens: undefined,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
return withContextRoot(<ShareProjectModal {...args} />, { project })
|
return <ShareProjectModal {...args} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NonAdminLinkSharingOff = args => {
|
export const NonAdminLinkSharingOff = args => {
|
||||||
const project = {
|
useScope({
|
||||||
...args.project,
|
project: {
|
||||||
publicAccesLevel: 'private',
|
...args.project,
|
||||||
}
|
publicAccesLevel: 'private',
|
||||||
|
},
|
||||||
return withContextRoot(<ShareProjectModal {...args} isAdmin={false} />, {
|
|
||||||
project,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return <ShareProjectModal {...args} isAdmin={false} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NonAdminLinkSharingOn = args => {
|
export const NonAdminLinkSharingOn = args => {
|
||||||
const project = {
|
useScope({
|
||||||
...args.project,
|
project: {
|
||||||
publicAccesLevel: 'tokenBased',
|
...args.project,
|
||||||
}
|
publicAccesLevel: 'tokenBased',
|
||||||
|
},
|
||||||
return withContextRoot(<ShareProjectModal {...args} isAdmin={false} />, {
|
|
||||||
project,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return <ShareProjectModal {...args} isAdmin={false} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RestrictedTokenMember = args => {
|
export const RestrictedTokenMember = args => {
|
||||||
|
@ -64,103 +73,64 @@ export const RestrictedTokenMember = args => {
|
||||||
// original value on unmount
|
// original value on unmount
|
||||||
// Currently this is necessary because the context value is set from window,
|
// Currently this is necessary because the context value is set from window,
|
||||||
// however in the future we should change this to set via props
|
// however in the future we should change this to set via props
|
||||||
const originalIsRestrictedTokenMember = window.isRestrictedTokenMember
|
|
||||||
window.isRestrictedTokenMember = true
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const originalIsRestrictedTokenMember = window.isRestrictedTokenMember
|
||||||
|
window.isRestrictedTokenMember = true
|
||||||
return () => {
|
return () => {
|
||||||
window.isRestrictedTokenMember = originalIsRestrictedTokenMember
|
window.isRestrictedTokenMember = originalIsRestrictedTokenMember
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const project = {
|
useScope({
|
||||||
...args.project,
|
project: {
|
||||||
publicAccesLevel: 'tokenBased',
|
...args.project,
|
||||||
}
|
publicAccesLevel: 'tokenBased',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
return withContextRoot(<ShareProjectModal {...args} />, { project })
|
return <ShareProjectModal {...args} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LegacyLinkSharingReadAndWrite = args => {
|
export const LegacyLinkSharingReadAndWrite = args => {
|
||||||
useFetchMock(setupFetchMock)
|
useFetchMock(setupFetchMock)
|
||||||
|
|
||||||
const project = {
|
useScope({
|
||||||
...args.project,
|
project: {
|
||||||
publicAccesLevel: 'readAndWrite',
|
...args.project,
|
||||||
}
|
publicAccesLevel: 'readAndWrite',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
return withContextRoot(<ShareProjectModal {...args} />, { project })
|
return <ShareProjectModal {...args} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LegacyLinkSharingReadOnly = args => {
|
export const LegacyLinkSharingReadOnly = args => {
|
||||||
useFetchMock(setupFetchMock)
|
useFetchMock(setupFetchMock)
|
||||||
|
|
||||||
const project = {
|
useScope({
|
||||||
...args.project,
|
project: {
|
||||||
publicAccesLevel: 'readOnly',
|
...args.project,
|
||||||
}
|
publicAccesLevel: 'readOnly',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
return withContextRoot(<ShareProjectModal {...args} />, { project })
|
return <ShareProjectModal {...args} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LimitedCollaborators = args => {
|
export const LimitedCollaborators = args => {
|
||||||
useFetchMock(setupFetchMock)
|
useFetchMock(setupFetchMock)
|
||||||
|
|
||||||
const project = {
|
useScope({
|
||||||
...args.project,
|
project: {
|
||||||
features: {
|
...args.project,
|
||||||
...args.project.features,
|
features: {
|
||||||
collaborators: 3,
|
...args.project.features,
|
||||||
|
collaborators: 3,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
})
|
||||||
|
|
||||||
return withContextRoot(<ShareProjectModal {...args} />, { project })
|
return <ShareProjectModal {...args} />
|
||||||
}
|
|
||||||
|
|
||||||
const project = {
|
|
||||||
_id: 'a-project',
|
|
||||||
name: 'A Project',
|
|
||||||
features: {
|
|
||||||
collaborators: -1, // unlimited
|
|
||||||
},
|
|
||||||
publicAccesLevel: 'private',
|
|
||||||
tokens: {
|
|
||||||
readOnly: 'ro-token',
|
|
||||||
readAndWrite: 'rw-token',
|
|
||||||
},
|
|
||||||
owner: {
|
|
||||||
_id: 'fakeOwnerId',
|
|
||||||
email: 'stories@overleaf.com',
|
|
||||||
},
|
|
||||||
members: [
|
|
||||||
{
|
|
||||||
_id: 'viewer-member',
|
|
||||||
type: 'user',
|
|
||||||
privileges: 'readOnly',
|
|
||||||
name: 'Viewer User',
|
|
||||||
email: 'viewer@example.com',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
_id: 'author-member',
|
|
||||||
type: 'user',
|
|
||||||
privileges: 'readAndWrite',
|
|
||||||
name: 'Author User',
|
|
||||||
email: 'author@example.com',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
invites: [
|
|
||||||
{
|
|
||||||
_id: 'test-invite-1',
|
|
||||||
privileges: 'readOnly',
|
|
||||||
name: 'Invited Viewer',
|
|
||||||
email: 'invited-viewer@example.com',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
_id: 'test-invite-2',
|
|
||||||
privileges: 'readAndWrite',
|
|
||||||
name: 'Invited Author',
|
|
||||||
email: 'invited-author@example.com',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -176,50 +146,9 @@ export default {
|
||||||
argTypes: {
|
argTypes: {
|
||||||
handleHide: { action: 'hide' },
|
handleHide: { action: 'hide' },
|
||||||
},
|
},
|
||||||
|
decorators: [ScopeDecorator],
|
||||||
}
|
}
|
||||||
|
|
||||||
const contacts = [
|
|
||||||
// user with edited name
|
|
||||||
{
|
|
||||||
type: 'user',
|
|
||||||
email: 'test-user@example.com',
|
|
||||||
first_name: 'Test',
|
|
||||||
last_name: 'User',
|
|
||||||
name: 'Test User',
|
|
||||||
},
|
|
||||||
// user with default name (email prefix)
|
|
||||||
{
|
|
||||||
type: 'user',
|
|
||||||
email: 'test@example.com',
|
|
||||||
first_name: 'test',
|
|
||||||
},
|
|
||||||
// no last name
|
|
||||||
{
|
|
||||||
type: 'user',
|
|
||||||
first_name: 'Eratosthenes',
|
|
||||||
email: 'eratosthenes@example.com',
|
|
||||||
},
|
|
||||||
// more users
|
|
||||||
{
|
|
||||||
type: 'user',
|
|
||||||
first_name: 'Claudius',
|
|
||||||
last_name: 'Ptolemy',
|
|
||||||
email: 'ptolemy@example.com',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'user',
|
|
||||||
first_name: 'Abd al-Rahman',
|
|
||||||
last_name: 'Al-Sufi',
|
|
||||||
email: 'al-sufi@example.com',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'user',
|
|
||||||
first_name: 'Nicolaus',
|
|
||||||
last_name: 'Copernicus',
|
|
||||||
email: 'copernicus@example.com',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
function setupFetchMock(fetchMock) {
|
function setupFetchMock(fetchMock) {
|
||||||
const delay = 1000
|
const delay = 1000
|
||||||
|
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
import { ContextRoot } from '../../js/shared/context/root-context'
|
|
||||||
import _ from 'lodash'
|
|
||||||
|
|
||||||
// Unfortunately, we cannot currently use decorators here, since we need to
|
|
||||||
// set a value on window, before the contexts are rendered.
|
|
||||||
// When using decorators, the contexts are rendered before the story, so we
|
|
||||||
// don't have the opportunity to set the window value first.
|
|
||||||
export function withContextRoot(Story, scope) {
|
|
||||||
const scopeWatchers = []
|
|
||||||
|
|
||||||
const ide = {
|
|
||||||
...window._ide,
|
|
||||||
$scope: {
|
|
||||||
...window._ide.$scope,
|
|
||||||
...scope,
|
|
||||||
$watch: (key, callback) => {
|
|
||||||
scopeWatchers.push([key, callback])
|
|
||||||
},
|
|
||||||
$applyAsync: callback => {
|
|
||||||
window.setTimeout(() => {
|
|
||||||
callback()
|
|
||||||
for (const [key, watcher] of scopeWatchers) {
|
|
||||||
watcher(_.get(ide.$scope, key))
|
|
||||||
}
|
|
||||||
}, 0)
|
|
||||||
},
|
|
||||||
$on: (eventName, callback) => {
|
|
||||||
//
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ContextRoot ide={ide} settings={{}}>
|
|
||||||
{Story}
|
|
||||||
</ContextRoot>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,6 +1,6 @@
|
||||||
import useFetchMock from './hooks/use-fetch-mock'
|
import useFetchMock from './hooks/use-fetch-mock'
|
||||||
import { withContextRoot } from './utils/with-context-root'
|
|
||||||
import WordCountModal from '../js/features/word-count-modal/components/word-count-modal'
|
import WordCountModal from '../js/features/word-count-modal/components/word-count-modal'
|
||||||
|
import { ScopeDecorator } from './decorators/scope'
|
||||||
|
|
||||||
const counts = {
|
const counts = {
|
||||||
headers: 4,
|
headers: 4,
|
||||||
|
@ -14,11 +14,6 @@ const messages = [
|
||||||
'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.',
|
'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.',
|
||||||
].join('\n')
|
].join('\n')
|
||||||
|
|
||||||
const project = {
|
|
||||||
_id: 'project-id',
|
|
||||||
name: 'A Project',
|
|
||||||
}
|
|
||||||
|
|
||||||
export const WordCount = args => {
|
export const WordCount = args => {
|
||||||
useFetchMock(fetchMock => {
|
useFetchMock(fetchMock => {
|
||||||
fetchMock.get(
|
fetchMock.get(
|
||||||
|
@ -28,7 +23,7 @@ export const WordCount = args => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
return withContextRoot(<WordCountModal {...args} />, { project })
|
return <WordCountModal {...args} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WordCountWithMessages = args => {
|
export const WordCountWithMessages = args => {
|
||||||
|
@ -40,7 +35,7 @@ export const WordCountWithMessages = args => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
return withContextRoot(<WordCountModal {...args} />, { project })
|
return <WordCountModal {...args} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ErrorResponse = args => {
|
export const ErrorResponse = args => {
|
||||||
|
@ -52,7 +47,7 @@ export const ErrorResponse = args => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
return withContextRoot(<WordCountModal {...args} />, { project })
|
return <WordCountModal {...args} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -61,4 +56,8 @@ export default {
|
||||||
args: {
|
args: {
|
||||||
show: true,
|
show: true,
|
||||||
},
|
},
|
||||||
|
argTypes: {
|
||||||
|
handleHide: { action: 'close modal' },
|
||||||
|
},
|
||||||
|
decorators: [ScopeDecorator],
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,8 @@
|
||||||
"modules/**/frontend/js/**/*.*",
|
"modules/**/frontend/js/**/*.*",
|
||||||
"test/frontend/**/*.*",
|
"test/frontend/**/*.*",
|
||||||
"modules/**/test/frontend/**/*.*",
|
"modules/**/test/frontend/**/*.*",
|
||||||
|
"frontend/stories/**/*.*",
|
||||||
|
"modules/**/stories/**/*.*",
|
||||||
"cypress",
|
"cypress",
|
||||||
"types"
|
"types"
|
||||||
]
|
]
|
||||||
|
|
7
services/web/types/folder.ts
Normal file
7
services/web/types/folder.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export type Folder = {
|
||||||
|
_id: string
|
||||||
|
name: string
|
||||||
|
docs: []
|
||||||
|
folders: []
|
||||||
|
fileRefs: []
|
||||||
|
}
|
30
services/web/types/project.ts
Normal file
30
services/web/types/project.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import { MongoUser } from './user'
|
||||||
|
import { Folder } from './folder'
|
||||||
|
|
||||||
|
type ProjectMember = {
|
||||||
|
_id: string
|
||||||
|
type: 'user'
|
||||||
|
privileges: 'readOnly' | 'readAndWrite'
|
||||||
|
name: string
|
||||||
|
email: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProjectInvite = {
|
||||||
|
_id: string
|
||||||
|
privileges: 'readOnly' | 'readAndWrite'
|
||||||
|
name: string
|
||||||
|
email: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Project = {
|
||||||
|
_id: string
|
||||||
|
name: string
|
||||||
|
features: Record<string, unknown>
|
||||||
|
publicAccesLevel?: string
|
||||||
|
tokens: Record<string, unknown>
|
||||||
|
owner: MongoUser
|
||||||
|
members: ProjectMember[]
|
||||||
|
invites: ProjectInvite[]
|
||||||
|
rootDocId?: string
|
||||||
|
rootFolder?: Folder[]
|
||||||
|
}
|
8
services/web/types/user.ts
Normal file
8
services/web/types/user.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
export type User = {
|
||||||
|
id: string
|
||||||
|
email: string
|
||||||
|
allowedFreeTrial?: boolean
|
||||||
|
features?: Record<string, boolean>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MongoUser = Pick<User, Exclude<keyof User, 'id'>> & { _id: string }
|
|
@ -15,5 +15,8 @@ declare global {
|
||||||
currentLangCode: string
|
currentLangCode: string
|
||||||
}
|
}
|
||||||
ExposedSettings: ExposedSettings
|
ExposedSettings: ExposedSettings
|
||||||
|
project_id: string
|
||||||
|
gitBridgePublicBaseUrl: string
|
||||||
|
_ide: Record<string, unknown>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue