first commit
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
import { useEffect } from 'react'
|
||||
import FileTreeContext from '../../../js/features/file-tree/components/file-tree-context'
|
||||
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 { useFileTreeActionable } from '../../../js/features/file-tree/contexts/file-tree-actionable'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
const defaultFileTreeContextProps = {
|
||||
refProviders: { mendeley: false, zotero: false },
|
||||
setRefProviderEnabled: provider => {
|
||||
console.log(`ref provider ${provider} enabled`)
|
||||
},
|
||||
setStartedFreeTrial: () => {
|
||||
console.log('started free trial')
|
||||
},
|
||||
initialSelectedEntityId: 'entity-1',
|
||||
onSelect: () => {
|
||||
console.log('selected')
|
||||
},
|
||||
}
|
||||
|
||||
export const mockCreateFileModalFetch = fetchMock =>
|
||||
fetchMock
|
||||
.get('path:/user/projects', {
|
||||
projects: [
|
||||
{
|
||||
_id: 'project-1',
|
||||
name: 'Project One',
|
||||
},
|
||||
{
|
||||
_id: 'project-2',
|
||||
name: 'Project Two',
|
||||
},
|
||||
],
|
||||
})
|
||||
.get('path:/mendeley/groups', {
|
||||
groups: [
|
||||
{
|
||||
id: 'group-1',
|
||||
name: 'Group One',
|
||||
},
|
||||
{
|
||||
id: 'group-2',
|
||||
name: 'Group Two',
|
||||
},
|
||||
],
|
||||
})
|
||||
.get('path:/zotero/groups', {
|
||||
groups: [
|
||||
{
|
||||
id: 'group-1',
|
||||
name: 'Group One',
|
||||
},
|
||||
{
|
||||
id: 'group-2',
|
||||
name: 'Group Two',
|
||||
},
|
||||
],
|
||||
})
|
||||
.get('express:/project/:projectId/entities', {
|
||||
entities: [
|
||||
{
|
||||
path: '/foo.tex',
|
||||
},
|
||||
{
|
||||
path: '/bar.tex',
|
||||
},
|
||||
],
|
||||
})
|
||||
.post('express:/project/:projectId/doc', (path, req) => {
|
||||
console.log({ path, req })
|
||||
return 204
|
||||
})
|
||||
.post('express:/project/:projectId/upload', (path, req) => {
|
||||
console.log({ path, req })
|
||||
return 204
|
||||
})
|
||||
.post('express:/project/:projectId/linked_file', (path, req) => {
|
||||
console.log({ path, req })
|
||||
return 204
|
||||
})
|
||||
|
||||
export const createFileModalDecorator =
|
||||
(fileTreeContextProps = {}, createMode = 'doc') =>
|
||||
// eslint-disable-next-line react/display-name
|
||||
Story => {
|
||||
return (
|
||||
<FileTreeContext
|
||||
{...defaultFileTreeContextProps}
|
||||
{...fileTreeContextProps}
|
||||
>
|
||||
<FileTreeCreateNameProvider>
|
||||
<FileTreeCreateFormProvider>
|
||||
<OpenCreateFileModal createMode={createMode}>
|
||||
<Story />
|
||||
</OpenCreateFileModal>
|
||||
</FileTreeCreateFormProvider>
|
||||
</FileTreeCreateNameProvider>
|
||||
</FileTreeContext>
|
||||
)
|
||||
}
|
||||
|
||||
function OpenCreateFileModal({ children, createMode }) {
|
||||
const { startCreatingFile } = useFileTreeActionable()
|
||||
|
||||
useEffect(() => {
|
||||
startCreatingFile(createMode)
|
||||
}, [createMode]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
return <>{children}</>
|
||||
}
|
||||
OpenCreateFileModal.propTypes = {
|
||||
createMode: PropTypes.string,
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.node,
|
||||
]).isRequired,
|
||||
}
|
@@ -0,0 +1,62 @@
|
||||
import {
|
||||
ModalFooterDecorator,
|
||||
ModalContentDecorator,
|
||||
} from '../modal-decorators'
|
||||
import { FileTreeModalCreateFileFooterContent } from '../../../js/features/file-tree/components/file-tree-create/file-tree-modal-create-file-footer'
|
||||
|
||||
export const Valid = args => <FileTreeModalCreateFileFooterContent {...args} />
|
||||
|
||||
export const Invalid = args => (
|
||||
<FileTreeModalCreateFileFooterContent {...args} />
|
||||
)
|
||||
Invalid.args = {
|
||||
valid: false,
|
||||
}
|
||||
|
||||
export const Inflight = args => (
|
||||
<FileTreeModalCreateFileFooterContent {...args} />
|
||||
)
|
||||
Inflight.args = {
|
||||
inFlight: true,
|
||||
}
|
||||
|
||||
export const FileLimitWarning = args => (
|
||||
<FileTreeModalCreateFileFooterContent {...args} />
|
||||
)
|
||||
FileLimitWarning.args = {
|
||||
fileCount: {
|
||||
status: 'warning',
|
||||
value: 1990,
|
||||
limit: 2000,
|
||||
},
|
||||
}
|
||||
|
||||
export const FileLimitError = args => (
|
||||
<FileTreeModalCreateFileFooterContent {...args} />
|
||||
)
|
||||
FileLimitError.args = {
|
||||
fileCount: {
|
||||
status: 'error',
|
||||
value: 2000,
|
||||
limit: 2000,
|
||||
},
|
||||
}
|
||||
|
||||
export default {
|
||||
title: 'Editor / Modals / Create File / Footer',
|
||||
component: FileTreeModalCreateFileFooterContent,
|
||||
args: {
|
||||
fileCount: {
|
||||
status: 'success',
|
||||
limit: 10,
|
||||
value: 1,
|
||||
},
|
||||
valid: true,
|
||||
inFlight: false,
|
||||
newFileCreateMode: 'doc',
|
||||
},
|
||||
argTypes: {
|
||||
cancel: { action: 'cancel' },
|
||||
},
|
||||
decorators: [ModalFooterDecorator, ModalContentDecorator],
|
||||
}
|
@@ -0,0 +1,89 @@
|
||||
import {
|
||||
createFileModalDecorator,
|
||||
mockCreateFileModalFetch,
|
||||
} from './create-file-modal-decorator'
|
||||
import FileTreeModalCreateFile from '../../../js/features/file-tree/components/modals/file-tree-modal-create-file'
|
||||
import useFetchMock from '../../hooks/use-fetch-mock'
|
||||
import { ScopeDecorator } from '../../decorators/scope'
|
||||
import { useScope } from '../../hooks/use-scope'
|
||||
import getMeta from '@/utils/meta'
|
||||
|
||||
export const MinimalFeatures = args => {
|
||||
useFetchMock(mockCreateFileModalFetch)
|
||||
Object.assign(getMeta('ol-ExposedSettings'), {
|
||||
hasLinkUrlFeature: false,
|
||||
hasLinkedProjectFileFeature: false,
|
||||
hasLinkedProjectOutputFileFeature: false,
|
||||
})
|
||||
|
||||
return <FileTreeModalCreateFile {...args} />
|
||||
}
|
||||
MinimalFeatures.decorators = [createFileModalDecorator()]
|
||||
|
||||
export const WithExtraFeatures = args => {
|
||||
useFetchMock(mockCreateFileModalFetch)
|
||||
|
||||
getMeta('ol-ExposedSettings').hasLinkUrlFeature = true
|
||||
|
||||
return <FileTreeModalCreateFile {...args} />
|
||||
}
|
||||
WithExtraFeatures.decorators = [
|
||||
createFileModalDecorator({
|
||||
refProviders: { mendeley: true, zotero: true },
|
||||
}),
|
||||
]
|
||||
|
||||
export const ErrorImportingFileFromExternalURL = args => {
|
||||
useFetchMock(fetchMock => {
|
||||
mockCreateFileModalFetch(fetchMock)
|
||||
|
||||
fetchMock.post('express:/project/:projectId/linked_file', 500)
|
||||
})
|
||||
|
||||
getMeta('ol-ExposedSettings').hasLinkUrlFeature = true
|
||||
|
||||
return <FileTreeModalCreateFile {...args} />
|
||||
}
|
||||
ErrorImportingFileFromExternalURL.decorators = [createFileModalDecorator()]
|
||||
|
||||
export const ErrorImportingFileFromReferenceProvider = args => {
|
||||
useFetchMock(fetchMock => {
|
||||
mockCreateFileModalFetch(fetchMock)
|
||||
|
||||
fetchMock.post('express:/project/:projectId/linked_file', 500)
|
||||
})
|
||||
|
||||
return <FileTreeModalCreateFile {...args} />
|
||||
}
|
||||
ErrorImportingFileFromReferenceProvider.decorators = [
|
||||
createFileModalDecorator({
|
||||
refProviders: { mendeley: true, zotero: true },
|
||||
}),
|
||||
]
|
||||
|
||||
export const FileLimitReached = args => {
|
||||
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} />
|
||||
}
|
||||
FileLimitReached.decorators = [createFileModalDecorator()]
|
||||
|
||||
export default {
|
||||
title: 'Editor / Modals / Create File',
|
||||
component: FileTreeModalCreateFile,
|
||||
decorators: [ScopeDecorator],
|
||||
}
|
@@ -0,0 +1,67 @@
|
||||
import FileTreeCreateNameInput from '../../../js/features/file-tree/components/file-tree-create/file-tree-create-name-input'
|
||||
import FileTreeCreateNameProvider from '../../../js/features/file-tree/contexts/file-tree-create-name'
|
||||
import {
|
||||
BlockedFilenameError,
|
||||
DuplicateFilenameError,
|
||||
} from '../../../js/features/file-tree/errors'
|
||||
import { ModalBodyDecorator, ModalContentDecorator } from '../modal-decorators'
|
||||
|
||||
export const DefaultLabel = args => (
|
||||
<FileTreeCreateNameProvider initialName="example.tex">
|
||||
<FileTreeCreateNameInput {...args} />
|
||||
</FileTreeCreateNameProvider>
|
||||
)
|
||||
|
||||
export const CustomLabel = args => (
|
||||
<FileTreeCreateNameProvider initialName="example.tex">
|
||||
<FileTreeCreateNameInput {...args} />
|
||||
</FileTreeCreateNameProvider>
|
||||
)
|
||||
CustomLabel.args = {
|
||||
label: 'File Name in this Project',
|
||||
}
|
||||
|
||||
export const FocusName = args => (
|
||||
<FileTreeCreateNameProvider initialName="example.tex">
|
||||
<FileTreeCreateNameInput {...args} />
|
||||
</FileTreeCreateNameProvider>
|
||||
)
|
||||
FocusName.args = {
|
||||
focusName: true,
|
||||
}
|
||||
|
||||
export const CustomPlaceholder = args => (
|
||||
<FileTreeCreateNameProvider>
|
||||
<FileTreeCreateNameInput {...args} />
|
||||
</FileTreeCreateNameProvider>
|
||||
)
|
||||
CustomPlaceholder.args = {
|
||||
placeholder: 'Enter a file name…',
|
||||
}
|
||||
|
||||
export const DuplicateError = args => (
|
||||
<FileTreeCreateNameProvider initialName="main.tex">
|
||||
<FileTreeCreateNameInput {...args} />
|
||||
</FileTreeCreateNameProvider>
|
||||
)
|
||||
DuplicateError.args = {
|
||||
error: new DuplicateFilenameError(),
|
||||
}
|
||||
|
||||
export const BlockedError = args => (
|
||||
<FileTreeCreateNameProvider initialName="main.tex">
|
||||
<FileTreeCreateNameInput {...args} />
|
||||
</FileTreeCreateNameProvider>
|
||||
)
|
||||
BlockedError.args = {
|
||||
error: new BlockedFilenameError(),
|
||||
}
|
||||
|
||||
export default {
|
||||
title: 'Editor / Modals / Create File / File Name Input',
|
||||
component: FileTreeCreateNameInput,
|
||||
decorators: [ModalBodyDecorator, ModalContentDecorator],
|
||||
args: {
|
||||
inFlight: false,
|
||||
},
|
||||
}
|
@@ -0,0 +1,106 @@
|
||||
import ErrorMessage from '../../../js/features/file-tree/components/file-tree-create/error-message'
|
||||
import { FetchError } from '../../../js/infrastructure/fetch-json'
|
||||
import {
|
||||
BlockedFilenameError,
|
||||
DuplicateFilenameError,
|
||||
InvalidFilenameError,
|
||||
} from '../../../js/features/file-tree/errors'
|
||||
|
||||
export const KeyedErrors = () => {
|
||||
return (
|
||||
<>
|
||||
<ErrorMessage error="name-exists" />
|
||||
<ErrorMessage error="too-many-files" />
|
||||
<ErrorMessage error="remote-service-error" />
|
||||
<ErrorMessage error="rate-limit-hit" />
|
||||
{/* <ErrorMessage error="not-logged-in" /> */}
|
||||
<ErrorMessage error="something-else" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const FetchStatusErrors = () => {
|
||||
return (
|
||||
<>
|
||||
<ErrorMessage
|
||||
error={
|
||||
new FetchError(
|
||||
'There was an error',
|
||||
'/story',
|
||||
{},
|
||||
new Response(null, { status: 400 })
|
||||
)
|
||||
}
|
||||
/>
|
||||
<ErrorMessage
|
||||
error={
|
||||
new FetchError(
|
||||
'There was an error',
|
||||
'/story',
|
||||
{},
|
||||
new Response(null, { status: 403 })
|
||||
)
|
||||
}
|
||||
/>
|
||||
<ErrorMessage
|
||||
error={
|
||||
new FetchError(
|
||||
'There was an error',
|
||||
'/story',
|
||||
{},
|
||||
new Response(null, { status: 429 })
|
||||
)
|
||||
}
|
||||
/>
|
||||
<ErrorMessage
|
||||
error={
|
||||
new FetchError(
|
||||
'There was an error',
|
||||
'/story',
|
||||
{},
|
||||
new Response(null, { status: 500 })
|
||||
)
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const FetchDataErrors = () => {
|
||||
return (
|
||||
<>
|
||||
<ErrorMessage
|
||||
error={
|
||||
new FetchError('Error', '/story', {}, new Response(), {
|
||||
message: 'There was an error!',
|
||||
})
|
||||
}
|
||||
/>
|
||||
<ErrorMessage
|
||||
error={
|
||||
new FetchError('Error', '/story', {}, new Response(), {
|
||||
message: {
|
||||
text: 'There was an error with some text!',
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const SpecificClassErrors = () => {
|
||||
return (
|
||||
<>
|
||||
<ErrorMessage error={new DuplicateFilenameError()} />
|
||||
<ErrorMessage error={new InvalidFilenameError()} />
|
||||
<ErrorMessage error={new BlockedFilenameError()} />
|
||||
<ErrorMessage error={new Error()} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default {
|
||||
title: 'Editor / Modals / Create File / Error Message',
|
||||
component: ErrorMessage,
|
||||
}
|
29
services/web/frontend/stories/modals/modal-decorators.jsx
Normal file
29
services/web/frontend/stories/modals/modal-decorators.jsx
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Wrap modal content in modal classes, without modal behaviours
|
||||
*/
|
||||
|
||||
export function ModalContentDecorator(Story) {
|
||||
return (
|
||||
<div className="modal-dialog">
|
||||
<div className="modal-content">
|
||||
<Story />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function ModalBodyDecorator(Story) {
|
||||
return (
|
||||
<div className="modal-body">
|
||||
<Story />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function ModalFooterDecorator(Story) {
|
||||
return (
|
||||
<div className="modal-footer">
|
||||
<Story />
|
||||
</div>
|
||||
)
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
import { Meta, StoryObj } from '@storybook/react'
|
||||
import { UnsavedDocsLockedAlert } from '@/features/ide-react/components/unsaved-docs/unsaved-docs-locked-alert'
|
||||
import { ScopeDecorator } from '../decorators/scope'
|
||||
|
||||
export default {
|
||||
title: 'Editor / Modals / Unsaved Docs Locked',
|
||||
component: UnsavedDocsLockedAlert,
|
||||
decorators: [Story => ScopeDecorator(Story)],
|
||||
} satisfies Meta
|
||||
|
||||
type Story = StoryObj<typeof UnsavedDocsLockedAlert>
|
||||
|
||||
export const Locked: Story = {}
|
Reference in New Issue
Block a user