Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Document Provider #4938

Draft
wants to merge 1 commit into
base: next
Choose a base branch
from
Draft

Conversation

ricokahler
Copy link
Contributor

@ricokahler ricokahler commented Sep 18, 2023

Description

This PR introduces a new provider called DocumentProvider, aiming to separate concerns and improve performance originally handled by the DocumentPaneProvider. It shifts core logic from the desk package into the core package and serves as a top-level interface for multiple other providers.

Context

The DocumentPaneProvider was identified as a performance bottleneck, updating too often and being depended upon by an increasing number of components. To solve this, we've split its responsibilities among more focused providers and moved the core logic from the desk package to the core package.

Features

  • Creation of a new DocumentProvider that serves as an interface for multiple other providers.
  • Migration of core logic from DocumentPaneProvider in desk package to DocumentProvider in core package.

Composition

The DocumentProvider is composed of the following providers:

  • FormStateProvider
  • InitialValueProvider
  • ReferenceInputOptionsProvider
  • TimelineProvider
  • DocumentIdAndTypeProvider

New Hooks

Several new hooks are introduced to work with the providers:

  • useFormState
  • useInitialValue
  • useReferenceInputOptions
  • useTimeline and useTimelineSelector
  • useDocumentId and useDocumentType

Note: Some of these hooks have versions that previously existed without associated providers. The new hooks are exported under a new package subpath sanity/document.

Example Usage

Here's how the new DocumentProvider is used alongside DocumentPaneProvider:

// Inside DocumentPane component
<DocumentProvider {...props}>
  <DocumentPaneProvider {...props}>
    <InnerDocumentPane />
  </DocumentPaneProvider>
</DocumentProvider>

And the internal composition of DocumentProvider:

<DocumentIdAndTypeProvider {...props}>
  <TimelineProvider {...props}>
    <InitialValueProvider {...props}>
      <ReferenceInputOptionsProvider {...props}>
        <FormStateProvider {...props}>
          {children}
        </FormStateProvider>
      </ReferenceInputOptionsProvider>
    </InitialValueProvider>
  </TimelineProvider>
</DocumentIdAndTypeProvider>

Migration Path

The only API here that's breaking is the internal useDocumentPane API which removes a lot of properties. Migrating involves using the newer hooks instead of just using the useDocumentPane API. The rest of this PR is more or less additive but I did deprecate with the @deprecate ts doc tag.

Form State Management

In addition to the the DocumentProvider, the FormStateProvider updates the API related to form state within the DocumentProvider. Some properties were updated, renamed, or moved in order to increase clarity. Feel free to look at this in depth more.

What to review

At this point, take a look at this PR conceptually and we can re-review with more details. This PR is still a bit early.

Notes for release

TBD.

@vercel
Copy link

vercel bot commented Sep 18, 2023

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
performance-studio ✅ Ready (Inspect) Visit Preview Sep 27, 2023 5:17pm
test-studio ✅ Ready (Inspect) Visit Preview 💬 Add feedback Sep 27, 2023 5:17pm
1 Ignored Deployment
Name Status Preview Comments Updated (UTC)
studio-workshop ⬜️ Ignored (Inspect) Visit Preview Sep 27, 2023 5:17pm

@github-actions
Copy link
Contributor

github-actions bot commented Sep 18, 2023

Package Documentation Change
sanity +4%
Full Report
sanity
This branch Next branch
114 documented 110 documented
829 not documented 834 not documented
@sanity/diff
This branch Next branch
13 documented 13 documented
16 not documented 16 not documented
@sanity/block-tools
This branch Next branch
4 documented 4 documented
9 not documented 9 not documented
@sanity/types
This branch Next branch
46 documented 46 documented
240 not documented 240 not documented
sanity/desk
This branch Next branch
83 documented 83 documented
54 not documented 54 not documented
@sanity/portable-text-editor
This branch Next branch
21 documented 21 documented
44 not documented 44 not documented
@sanity/mutator
This branch Next branch
7 documented 7 documented
4 not documented 4 not documented
@sanity/cli
This branch Next branch
1 documented 1 documented
30 not documented 30 not documented
sanity/document
This branch Next branch
1 documented 0 documented
16 not documented 0 not documented
@sanity/schema/_internal
This branch Next branch
0 documented 0 documented
9 not documented 9 not documented
@sanity/util/paths
This branch Next branch
1 documented 1 documented
15 not documented 15 not documented
sanity/router
This branch Next branch
15 documented 15 documented
21 not documented 21 not documented
@sanity/util/legacyDateFormat
This branch Next branch
0 documented 0 documented
3 not documented 3 not documented
@sanity/schema
This branch Next branch
0 documented 0 documented
2 not documented 2 not documented
sanity/cli
This branch Next branch
2 documented 2 documented
0 not documented 0 not documented
@sanity/vision
This branch Next branch
0 documented 0 documented
2 not documented 2 not documented
@sanity/util/fs
This branch Next branch
0 documented 0 documented
3 not documented 3 not documented
sanity/_internal
This branch Next branch
0 documented 0 documented
1 not documented 1 not documented
@sanity/util/content
This branch Next branch
1 documented 1 documented
5 not documented 5 not documented

@github-actions
Copy link
Contributor

github-actions bot commented Sep 18, 2023

Component Testing Report Updated Sep 27, 2023 5:19 PM (UTC)

File Status Duration Passed Skipped Failed
formBuilder/ArrayInput.spec.tsx ✅ Passed (Inspect) 7s 3 0 0
formBuilder/inputs/PortableText/Annotations.spec.tsx ✅ Passed (Inspect) 11s 3 0 0
formBuilder/inputs/PortableText/Decorators.spec.tsx ✅ Passed (Inspect) 11s 6 0 0
formBuilder/inputs/PortableText/Input.spec.tsx ✅ Passed (Inspect) 15s 9 0 0
formBuilder/inputs/PortableText/ObjectBlock.spec.tsx ✅ Passed (Inspect) 49s 18 0 0
formBuilder/inputs/PortableText/Styles.spec.tsx ✅ Passed (Inspect) 11s 6 0 0
formBuilder/inputs/PortableText/Toolbar.spec.tsx ✅ Passed (Inspect) 6s 3 0 0

Copy link
Member

@bjoerge bjoerge left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great! I love the general direction this is going.

Some quick general notes:

  • useFormState and useEditState feels very similar in nature and I think the existence of both might potentially add some confusion. Would it make sense to introduce an useEditState that takes no arguments and reads from a context provider instead? If so we can also consider removing providing editState from the FormStateProvider. The current useEditState(<id>, <type>) is marked as internal, but I suspect it might be breaking to remove it, so perhaps it can be deprecated and still live on while we introduce a new one without any arguments? We might even be able to give the new one a better name. (Note: I don't have a crystal clear picture of whether this makes sense, so consider it with a grain of salt)

  • Bug: When running the branch locally and I open a document, it loads fine, but after a few secs it goes back into loading state which stays forever.

  • See also inline comments.

import {EditStateFor, PermissionCheckResult} from '../../store'

export interface FormStateContextValue<TDocument extends SanityDocumentLike> {
documentId: string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should probably document whether this is the draft id or published id

*/
value: TDocument

editState: EditStateFor
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use the opportunity to rename EditStateFor => DocumentEditState ?


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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does "edit states" refer to here? IIRC, the edit state is already a downstream dependency of connection state. Also wondering whether "timeline ready" state should be kept separate (e.g. we don't need the timeline to be ready in order to edit I believe)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bjoerge and i were talking about this in a meeting and we feel like ready does not communicate exactly what is ready and we may rename this one.

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be named onPatchValue or simply onPatch to match already established conventions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bjoerge and I discussed this in a call as well and we think we like the convention handle- instead of on to signify that the caller can call this function instead of having to provide an implementation

formState: DocumentFormNode | null

focusPath: Path
setFocusPath: (path: Path) => void
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be named onPathFocus for consistency with existing APIs

* 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would there be cases where you need a combination of several of these? I think it's great that we only have one value to refer to as the document currently being edited, but wondering if it will make it hard to know e.g. whether there exists a published version of the draft currently being edited.


/**
* 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

current-value isn't valid for valueOrigin?

* 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be good to document what cases valueOrigin may be undefined

export function DocumentIdAndTypeProvider({
fallback,
children,
documentType: typeFromProps = '*',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does * mean in this context? Could it be replaced by undefined or null?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed. we shouldn't use constants and do string compares when we already have to do null checks

@@ -1,7 +1,7 @@
import {SanityClient} from '@sanity/client'
import {IdPair} from '../types'

export function memoizeKeyGen(client: SanityClient, idPair: IdPair, typeName: string) {
export function memoizeKeyGen(client: SanityClient, idPair: IdPair, typeName = '*'): string {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this create separate cache entries for the same document id? I.e. depending on whether or not we know the document type up-front? If so, I'm wondering if we should require the type to be known at this point and do the lookup at an earlier stage

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self-note for future reference: i think this change is not necessary atm. this change was introduced to allow the useConnectionState hook to not have a specified type however this may not be the time for that change so we'll add a onConnectionStateChanged prop to the DocumentProvider instead to make this work

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants