Skip to content

Commit

Permalink
feat(visual-editing): add svelte optimistic state support (#2307)
Browse files Browse the repository at this point in the history
* feat(visual-editing): add optimistic export path

* feat(visual-editing): add svelte optimistic state support

* chore(apps): use svelte optimistic state

* chore: migrate useDatasetMutator hook

* chore: fix types and merge conflict regressions

* chore: handle deprecated exports

---------

Co-authored-by: Cody Olsen <stipsan@gmail.com>
  • Loading branch information
rdunk and stipsan authored Dec 19, 2024
1 parent f3f4f2a commit 73c7247
Show file tree
Hide file tree
Showing 25 changed files with 432 additions and 139 deletions.
16 changes: 15 additions & 1 deletion apps/svelte/src/components/Shoe.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
<script lang="ts">
import {PortableText} from '@portabletext/svelte'
import type {SanityDocument} from '@sanity/client'
import {stegaClean} from '@sanity/client/stega'
import {createDataAttribute} from '@sanity/visual-editing'
import {useOptimistic} from '@sanity/visual-editing/svelte'
import {page} from '$app/stores'
import type {ShoeResult} from '$lib/queries'
import {urlFor, urlForCrossDatasetReference} from '$lib/sanity'
Expand All @@ -13,7 +15,19 @@
const parentPath = $page.url.pathname.split('/').slice(0, -1).join('/')
$: [coverImage, ...otherImages] = product?.media || []
const {value: media, update: updateMedia} = useOptimistic<
ShoeResult['media'],
SanityDocument<ShoeResult>
>(product.media, (state, action) => {
if (action.id === product._id && action.document.media) {
return action.document.media
}
return state
})
$: updateMedia(product.media)
$: [coverImage, ...otherImages] = $media || []
</script>

<svelte:head>
Expand Down
9 changes: 9 additions & 0 deletions packages/visual-editing/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@
"require": "./dist/next-pages-router/index.cjs",
"default": "./dist/next-pages-router/index.js"
},
"./optimistic": {
"source": "./src/optimistic/index.ts",
"import": "./dist/optimistic/index.js",
"require": "./dist/optimistic/index.cjs",
"default": "./dist/optimistic/index.js"
},
"./react": {
"source": "./src/react/index.ts",
"import": "./dist/react/index.js",
Expand Down Expand Up @@ -82,6 +88,9 @@
"next-pages-router": [
"./dist/next-pages-router/index.d.ts"
],
"optimistic": [
"./dist/optimistic/index.d.ts"
],
"react": [
"./dist/react/index.d.ts"
],
Expand Down
100 changes: 87 additions & 13 deletions packages/visual-editing/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
import type {DatasetMutatorMachineInput as DatasetMutatorMachineInputDeprecated} from './optimistic/state/datasetMutator'
import type {
DocumentsGet as DocumentsGetDeprecated,
DocumentsMutate as DocumentsMutateDeprecated,
OptimisticDocument as OptimisticDocumentDeprecated,
OptimisticDocumentPatches as OptimisticDocumentPatchesDeprecated,
OptimisticReducerAction as OptimisticReducerActionDeprecated,
OptimisticReducer as OptimisticReducerDeprecated,
Path as PathDeprecated,
PathValue as PathValueDeprecated,
} from './optimistic/types'
import {useDocuments as useDocumentsDeprecated} from './react/useDocuments'
import {useOptimistic as useOptimisticDeprecated} from './react/useOptimistic'

export {createOverlayController} from './controller'
export type {
DisableVisualEditing,
Expand Down Expand Up @@ -53,19 +67,6 @@ export type {
VisualEditingOptions,
} from './types'
export {enableVisualEditing} from './ui/enableVisualEditing'
export type {DatasetMutatorMachineInput} from './ui/optimistic-state/machines/datasetMutatorMachine'
export {
type DocumentsGet,
type DocumentsMutate,
type OptimisticDocument,
type OptimisticDocumentPatches,
type OptimisticReducer,
type OptimisticReducerAction,
type Path,
type PathValue,
useDocuments,
useOptimistic,
} from './ui/optimistic-state'
export {useSharedState} from './ui/shared-state/useSharedState'
export {
type CreateDataAttribute,
Expand All @@ -89,3 +90,76 @@ export {
createDataAttribute,
} from '@repo/visual-editing-helpers'
export {getArrayItemKeyAndParentPath} from './util/mutations'

/**
* @public
* @deprecated Use `import {useDocuments} from '@sanity/visual-editing/react'` instead
*/
export const useDocuments = useDocumentsDeprecated
/**
* @public
* @deprecated Use `import {useOptimistic} from '@sanity/visual-editing/react'` instead
*/
export const useOptimistic = useOptimisticDeprecated
/**
* @public
* @deprecated Use `import type {DatasetMutatorMachineInput} from '@sanity/visual-editing/optimistic'` instead
*/
export type DatasetMutatorMachineInput = DatasetMutatorMachineInputDeprecated
/**
* @public
* @deprecated Use `import type {DocumentsGet} from '@sanity/visual-editing/optimistic'` instead
*/
export type DocumentsGet = DocumentsGetDeprecated
/**
* @public
* @deprecated Use `import type {DocumentsMutate} from '@sanity/visual-editing/optimistic'` instead
*/
export type DocumentsMutate = DocumentsMutateDeprecated
/**
* @public
* @deprecated Use `import type {OptimisticDocument} from '@sanity/visual-editing/optimistic'` instead
*/
export type OptimisticDocument = OptimisticDocumentDeprecated
/**
* @public
* @deprecated Use `import type {OptimisticDocumentPatches} from '@sanity/visual-editing/optimistic'` instead
*/
export type OptimisticDocumentPatches = OptimisticDocumentPatchesDeprecated
/**
* @public
* @deprecated Use `import type {OptimisticReducer} from '@sanity/visual-editing/optimistic'` instead
*/
export type OptimisticReducer<T, U> = OptimisticReducerDeprecated<T, U>
/**
* @public
* @deprecated Use `import type {OptimisticReducerAction} from '@sanity/visual-editing/optimistic'` instead
*/
export type OptimisticReducerAction<T> = OptimisticReducerActionDeprecated<T>
/**
* @public
* @deprecated Use `import type {Path} from '@sanity/visual-editing/optimistic'` instead
*/
export type Path<T, K extends keyof T> = PathDeprecated<T, K>
/**
* @public
* @deprecated Use `import type {PathValue} from '@sanity/visual-editing/optimistic'` instead
*/
export type PathValue<T, P extends string> = PathValueDeprecated<T, P>
/**
* @internal
* @deprecated - do not use
*/
export type {
useDocumentsDeprecated,
useOptimisticDeprecated,
DatasetMutatorMachineInputDeprecated,
DocumentsGetDeprecated,
DocumentsMutateDeprecated,
OptimisticDocumentDeprecated,
OptimisticDocumentPatchesDeprecated,
OptimisticReducerDeprecated,
OptimisticReducerActionDeprecated,
PathDeprecated,
PathValueDeprecated,
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {createEmptyActor, type ActorRefFrom} from 'xstate'
import {createDatasetMutator} from '../comlink'
import {createDatasetMutator} from './state/datasetMutator'

export type MutatorActor = ActorRefFrom<ReturnType<typeof createDatasetMutator>>
export type EmptyActor = typeof emptyActor
Expand Down
54 changes: 54 additions & 0 deletions packages/visual-editing/src/optimistic/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
export type {VisualEditingNode} from '../types'
export type {
DocumentSchema,
HistoryRefresh,
HistoryUpdate,
PreviewSnapshot,
ResolvedSchemaTypeMap,
SanityNode,
SanityStegaNode,
SchemaArrayItem,
SchemaArrayNode,
SchemaBooleanNode,
SchemaInlineNode,
SchemaNode,
SchemaNullNode,
SchemaNumberNode,
SchemaObjectField,
SchemaObjectNode,
SchemaStringNode,
SchemaType,
SchemaUnionNode,
SchemaUnionNodeOptions,
SchemaUnionOption,
SchemaUnknownNode,
Serializable,
SerializableArray,
SerializableObject,
SerializablePrimitive,
TypeSchema,
UnresolvedPath,
VisualEditingControllerMsg,
VisualEditingNodeMsg,
} from '@repo/visual-editing-helpers'
export type {
DocumentsGet,
DocumentsMutate,
OptimisticDocument,
OptimisticDocumentPatches,
OptimisticReducerAction,
Path,
PathValue,
OptimisticReducer,
} from './types'
export {
type MutatorActor,
type EmptyActor,
emptyActor,
actor,
listeners,
isEmptyActor,
setActor,
} from './context'
export {createDatasetMutator, type DatasetMutatorMachineInput} from './state/datasetMutator'
export {createDocumentMutator} from './state/documentMutator'
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {type ListenEvent} from '@sanity/client'
import {merge, ReplaySubject, Subject, type Observable, type ObservedValueOf} from 'rxjs'
import type {VisualEditingNode} from '../../../types'
import type {VisualEditingNode} from '../../types'

// eslint-disable-next-line @typescript-eslint/no-explicit-any
type SharedListenEvent = ListenEvent<Record<string, any>>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import {
type DocumentMutatorMachineParentEvent,
} from '@sanity/mutate/_unstable_machine'
import {assertEvent, assign, emit, setup, stopChild, type ActorRefFrom} from 'xstate'
import type {createDocumentMutator} from '../../comlink'
import type {VisualEditingNode} from '../../types'
import {createDocumentMutator} from './documentMutator'

export interface DatasetMutatorMachineInput extends Omit<DocumentMutatorMachineInput, 'id'> {
client: SanityClient
Expand Down Expand Up @@ -118,3 +119,12 @@ export const datasetMutatorMachine = setup({
pristine: {},
},
})

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const createDatasetMutator = (comlink: VisualEditingNode) => {
return datasetMutatorMachine.provide({
actors: {
documentMutatorMachine: createDocumentMutator(comlink),
},
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
} from '@sanity/mutate/_unstable_machine'
import {enqueueActions, fromPromise} from 'xstate'
import type {VisualEditingNode} from '../../types'
import {datasetMutatorMachine} from '../optimistic-state/machines/datasetMutatorMachine'

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const createDocumentMutator = (comlink: VisualEditingNode) => {
Expand Down Expand Up @@ -56,12 +55,3 @@ export const createDocumentMutator = (comlink: VisualEditingNode) => {
},
})
}

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const createDatasetMutator = (comlink: VisualEditingNode) => {
return datasetMutatorMachine.provide({
actors: {
documentMutatorMachine: createDocumentMutator(comlink),
},
})
}
77 changes: 77 additions & 0 deletions packages/visual-editing/src/optimistic/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type {SanityDocument} from '@sanity/client'
import {type Mutation, type NodePatchList} from '@sanity/mutate'

export type Path<T, K extends keyof T> = K extends string
? T[K] extends Record<string, any>
? `${K}.${Path<T[K], keyof T[K]>}` | K
: K
: never

export type PathValue<T, P extends string> = P extends `${infer K}.${infer Rest}`
? K extends keyof T
? PathValue<T[K], Rest>
: never
: P extends keyof T
? T[P]
: never

export type DocumentsMutate = (
documentId: string,
mutations: Mutation[],
options?: {commit?: boolean | {debounce: number}},
) => void

export type DocumentsGet = <T extends Record<string, any>>(
documentId: string,
) => OptimisticDocument<T>

export type OptimisticDocumentPatches<T extends Record<string, any> = Record<string, any>> =
| ((context: {
draftId: string
publishedId: string
/**
* @deprecated - use `getSnapshot` instead
*/
snapshot: SanityDocument<T> | undefined
getSnapshot: () => Promise<SanityDocument<T> | null>
}) => Promise<NodePatchList> | NodePatchList)
| NodePatchList

export type OptimisticDocument<T extends Record<string, any> = Record<string, any>> = {
/**
* The document ID
*/
id: string
/**
* Commits any locally applied mutations to the remote document
*/
commit: () => void
/**
* @deprecated - use `getSnapshot` instead
*/
get: {
(): SanityDocument<T> | undefined
<P extends Path<T, keyof T>>(path: P): PathValue<T, P> | undefined
}
/**
* Returns a promise that resolves to the current document snapshot
*/
getSnapshot: () => Promise<SanityDocument<T> | null>
/**
* Applies the given patches to the document
*/
patch: (
patches: OptimisticDocumentPatches<T>,
options?: {commit?: boolean | {debounce: number}},
) => void
}

export type OptimisticReducerAction<T> = {
document: T
id: string
originalId: string
type: 'appear' | 'mutate' | 'disappear'
}

export type OptimisticReducer<T, U> = (state: T, action: OptimisticReducerAction<U>) => T
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import {
type MouseEvent,
} from 'react'
import {styled} from 'styled-components'
import {useDocuments} from '../../react/useDocuments'
import type {ElementNode, OverlayComponent} from '../../types'
import {useDocuments} from '../../ui/optimistic-state/useDocuments'
import {getArrayInsertPatches} from '../../util/mutations'
import {InsertMenuPopover} from './InsertMenu'

Expand Down
Loading

0 comments on commit 73c7247

Please sign in to comment.