first commit

This commit is contained in:
2025-04-24 13:11:28 +08:00
commit ff9c54d5e4
5960 changed files with 834111 additions and 0 deletions

View File

@@ -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,
}

View File

@@ -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],
}

View File

@@ -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],
}

View File

@@ -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,
},
}

View File

@@ -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,
}

View 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>
)
}

View File

@@ -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 = {}