Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
ricokahler committed Sep 18, 2023
1 parent f7f92bf commit f0dcfea
Show file tree
Hide file tree
Showing 68 changed files with 1,627 additions and 1,069 deletions.
1 change: 1 addition & 0 deletions packages/sanity/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
/cli.js
/desk.js
/router.js
/document.js

# Playwright-ct artifacts
/playwright-ct/report
Expand Down
1 change: 1 addition & 0 deletions packages/sanity/exports/document.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from '../src/core/document'
11 changes: 11 additions & 0 deletions packages/sanity/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,17 @@
"import": "./lib/index.esm.js",
"default": "./lib/index.esm.js"
},
"./document": {
"types": "./lib/exports/document.d.ts",
"source": "./exports/document.ts",
"require": "./lib/document.js",
"node": {
"import": "./lib/document.cjs.mjs",
"require": "./lib/document.js"
},
"import": "./lib/document.esm.js",
"default": "./lib/document.esm.js"
},
"./_internal": {
"types": "./lib/exports/_internal.d.ts",
"source": "./exports/_internal.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/sanity/src/core/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export interface Tool<Options = any> {
options?: Options

/**
* The router for the tool. See {@link router.Router}
* The router for the tool. See {@link Router}
*/
router?: Router

Expand Down
5 changes: 5 additions & 0 deletions packages/sanity/src/core/document/DocumentContextError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export class DocumentContextError extends Error {
constructor() {
super('Could not find context value. Did you wrap this component in a <Document.Provider />?')
}
}
10 changes: 10 additions & 0 deletions packages/sanity/src/core/document/DocumentIdAndTypeContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import {createContext} from 'react'

export interface DocumentIdAndTypeContextValue {
/** the published ID */
documentId: string
/** the resolved document */
documentType: string
}

export const DocumentIdAndTypeContext = createContext<DocumentIdAndTypeContextValue | null>(null)
88 changes: 88 additions & 0 deletions packages/sanity/src/core/document/DocumentIdAndTypeProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import React, {useEffect, useMemo, useState} from 'react'
import {first} from 'rxjs'
import {getPublishedId} from '../util'
import {useTemplates} from '../hooks'
import {useDocumentStore} from '../store'
import {Template} from '../templates'
import {DocumentIdAndTypeContext, DocumentIdAndTypeContextValue} from './DocumentIdAndTypeContext'

export interface DocumentIdAndTypeProviderProps {
documentId: string
documentType: string | undefined
templateName: string | undefined
children: React.ReactNode
fallback: React.ReactNode
}

export function DocumentIdAndTypeProvider({
fallback,
children,
documentType: typeFromProps = '*',
documentId: idFromProps,
templateName,
}: DocumentIdAndTypeProviderProps) {
const documentId = getPublishedId(idFromProps)
const templates = useTemplates()
const documentStore = useDocumentStore()

const [error, setError] = useState<Error | null>(null)
if (error) throw error

// generate a lookup object for templates by their IDs
const templatesById = useMemo(() => {
return templates.reduce<Record<string, Template>>((acc, t) => {
acc[t.id] = t
return acc
}, {})
}, [templates])

// initialize document type based on provided props or template schema.
// falls back to null if neither is available.
const [documentType, setDocumentType] = useState<string | null>(() => {
if (typeFromProps && typeFromProps !== '*') return typeFromProps

const typeFromTemplates = templateName && templatesById[templateName]?.schemaType
if (typeFromTemplates) return typeFromTemplates

return null
})

useEffect(() => {
// exit early if document type is already determined.
if (documentType) return undefined

// fetch and set the document type from the document store
const subscription = documentStore
.resolveTypeForDocument(documentId)
// note: this operation is only done once to maintain consistency with
// other non-reactive code paths.
.pipe(first())
.subscribe({
next: (documentTypeFromContentLake) => {
if (documentTypeFromContentLake) {
setDocumentType(documentTypeFromContentLake)
} else {
setError(
new Error(`Could not resolve document type for document with ID ${documentId}`),
)
}
},
error: setError,
})

return () => subscription.unsubscribe()
}, [documentId, documentStore, documentType])

if (!documentType) return <>{fallback}</>

const contextValue: DocumentIdAndTypeContextValue = {
documentId,
documentType,
}

return (
<DocumentIdAndTypeContext.Provider value={contextValue}>
{children}
</DocumentIdAndTypeContext.Provider>
)
}
69 changes: 69 additions & 0 deletions packages/sanity/src/core/document/DocumentProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React from 'react'
import {noop} from 'lodash'
import {InitialValueProvider, InitialValueProviderProps} from './initialValue'
import {
DocumentIdAndTypeProvider,
DocumentIdAndTypeProviderProps,
} from './DocumentIdAndTypeProvider'
import {TimelineProvider, TimelineProviderProps} from './timeline'
import {FormStateProvider, FormStateProviderProps} from './formState'
import {
ReferenceInputOptionsProvider,
ReferenceInputOptionsProviderProps,
} from './referenceInputOptions'

/** @internal */
export interface DocumentProviderProps
extends DocumentIdAndTypeProviderProps,
TimelineProviderProps,
InitialValueProviderProps,
FormStateProviderProps,
ReferenceInputOptionsProviderProps {}

/** @internal */
export function DocumentProvider({
documentId,
documentType,
templateName,
templateParams,
timelineRange = {},
onTimelineRangeChange = noop,
initialFocusPath,
isHistoryInspectorOpen,
fallback,
children,
EditReferenceLinkComponent,
onEditReference,
activePath,
}: DocumentProviderProps) {
return (
<DocumentIdAndTypeProvider
documentId={documentId}
documentType={documentType}
templateName={templateName}
fallback={fallback}
>
<TimelineProvider timelineRange={timelineRange} onTimelineRangeChange={onTimelineRangeChange}>
<InitialValueProvider
templateName={templateName}
templateParams={templateParams}
fallback={fallback}
>
<ReferenceInputOptionsProvider
EditReferenceLinkComponent={EditReferenceLinkComponent}
onEditReference={onEditReference}
activePath={activePath}
fallback={fallback}
>
<FormStateProvider
initialFocusPath={initialFocusPath}
isHistoryInspectorOpen={isHistoryInspectorOpen}
>
{children}
</FormStateProvider>
</ReferenceInputOptionsProvider>
</InitialValueProvider>
</TimelineProvider>
</DocumentIdAndTypeProvider>
)
}
83 changes: 83 additions & 0 deletions packages/sanity/src/core/document/formState/FormStateContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import {SanityDocumentLike, ObjectSchemaType, ValidationMarker, Path} from '@sanity/types'
import {createContext} from 'react'
import {PatchEvent, DocumentFormNode, StateTree} from '../../form'
import {ConnectionState} from '../../hooks'
import {EditStateFor, PermissionCheckResult} from '../../store'

export interface FormStateContextValue<TDocument extends SanityDocumentLike> {
documentId: string
documentType: string

/**
* the current value of the form. note that this supersedes both the `value`
* and the `displayed` prop from the previous `DocumentPaneContextValue`
* interface
*/
value: TDocument

editState: EditStateFor

/**
* if the value originates from the current value from context lake, then this
* will be `current-value`. if the value is from a historical revision then
* it will be `historical-value`, if the value is from an initial value
* template then it will be `initial-value`
*/
valueOrigin: 'draft-value' | 'published-value' | 'initial-value' | 'historical-value' | undefined

schemaType: ObjectSchemaType

/**
* The value that is used to compare changes since a particular time. This is
* used to show change indicators. For example, this value is typically the
* published version of the document so that while you're editing the draft,
* the published version can be compared to the current draft version.
*/
compareValue: TDocument | null

/**
* Signals when the document is ready for editing. Considers the connection
* state, edit states, and whether or not the timeline is ready.
*/
ready: boolean

/**
* Propagates changes described by a patch event message to the form value.
*/
patchValue: (event: PatchEvent) => void

/**
* Contains the prepared root form node state. This is the result of
* `prepareFormState`.
*/
formState: DocumentFormNode | null

focusPath: Path
setFocusPath: (path: Path) => void

openPath: Path
setOpenPath: (path: Path) => void

collapsedFieldsets: StateTree<boolean>
setFieldsetCollapsed: (path: Path, collapsed: boolean) => void

collapsedPaths: StateTree<boolean>
setPathCollapsed: (path: Path, collapsed: boolean) => void

activeFieldGroups: StateTree<string>
setActiveFieldGroup: (path: Path, groupName: string) => void

validation: ValidationMarker[]
permissions: PermissionCheckResult | undefined
isPermissionsLoading: boolean

connectionState: ConnectionState

delete: () => void
isDeleting: boolean
isDeleted: boolean
}

export const FormStateContext = createContext<FormStateContextValue<SanityDocumentLike> | null>(
null,
)
Loading

0 comments on commit f0dcfea

Please sign in to comment.