-
Notifications
You must be signed in to change notification settings - Fork 394
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[base] Add infrastructure for document-actions
Co-authored-by: Per-Kristian Nordnes <per.kristian.nordnes@gmail.com>
- Loading branch information
1 parent
68a80c9
commit 87d9299
Showing
36 changed files
with
1,118 additions
and
167 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
83 changes: 83 additions & 0 deletions
83
packages/@sanity/base/src/actions/utils/RenderActionCollectionState.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
/* eslint-disable react/no-multi-comp */ | ||
import * as React from 'react' | ||
import {ActionDescription} from './types' | ||
|
||
interface RenderActionCollectionProps { | ||
actions: any[] | ||
actionProps: any | ||
onActionComplete: () => void | ||
component: (args: {actionStates: ActionDescription[]}) => React.ReactNode | ||
} | ||
|
||
const actionIds = new WeakMap() | ||
|
||
let counter = 0 | ||
const getActionId = action => { | ||
if (actionIds.has(action)) { | ||
return actionIds.get(action) | ||
} | ||
const id = `${action.name || action.displayName || '<anonymous>'}-${counter++}` | ||
actionIds.set(action, id) | ||
return id | ||
} | ||
|
||
export function RenderActionCollectionState(props: RenderActionCollectionProps) { | ||
const [actionsWithStates, setActionsWithState] = React.useState([]) | ||
const [keys, setKey] = React.useState({}) | ||
const handleComplete = React.useCallback( | ||
id => { | ||
setKey(keys => ({...keys, [id]: (keys[id] || 0) + 1})) | ||
props.onActionComplete() | ||
}, | ||
[props.actions] | ||
) | ||
|
||
const onStateChange = React.useCallback( | ||
stateUpdate => { | ||
setActionsWithState(prevState => { | ||
return props.actions.map((action: any) => { | ||
const id = getActionId(action) | ||
return stateUpdate[0] === id | ||
? [id, stateUpdate[1]] | ||
: prevState.find(prev => prev[0] === id) || [id] | ||
}) | ||
}) | ||
}, | ||
[props.actions] | ||
) | ||
|
||
const {actions: _, actionProps, component, ...rest} = props | ||
|
||
return ( | ||
<> | ||
{component({ | ||
actionStates: actionsWithStates | ||
.map(([id, state]) => state && {...state, actionId: id}) | ||
.filter(Boolean), | ||
...rest | ||
})} | ||
|
||
{props.actions.map(action => { | ||
const actionId = getActionId(action) | ||
return ( | ||
<ActionStateContainer | ||
key={`${actionId}-${keys[actionId] || '0'}`} | ||
action={action} | ||
id={actionId} | ||
actionProps={props.actionProps} | ||
onUpdate={onStateChange} | ||
onComplete={handleComplete} | ||
/> | ||
) | ||
})} | ||
</> | ||
) | ||
} | ||
|
||
const ActionStateContainer = React.memo(function ActionStateContainer(props: any) { | ||
const {id, action, onUpdate, onComplete, actionProps} = props | ||
|
||
const state = action({...actionProps, onComplete: () => onComplete(id)}) | ||
onUpdate([id, state ? state : null]) | ||
return null | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export {RenderActionCollectionState} from './RenderActionCollectionState' |
15 changes: 13 additions & 2 deletions
15
...nity/base/src/util/documentActionUtils.js → ...tions/utils/legacy_documentActionUtils.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import React from 'react' | ||
|
||
export interface ActionComponent<ActionProps> { | ||
(props: ActionProps): ActionDescription | ||
} | ||
|
||
interface Document { | ||
_id: string | ||
_type: string | ||
} | ||
|
||
interface DocumentActionProps { | ||
id: string | ||
type: string | ||
draft: null | Document | ||
published: null | Document | ||
onComplete: () => void | ||
} | ||
|
||
export type DocumentActionComponent = ActionComponent<DocumentActionProps> | ||
|
||
interface ConfirmDialogProps { | ||
type: 'confirm' | ||
color?: 'warning' | 'success' | 'danger' | 'info' | ||
message: React.ReactNode | ||
onConfirm: () => void | ||
onCancel: () => void | ||
} | ||
|
||
// Todo: move these to action spec/core types | ||
interface ModalDialogProps { | ||
type: 'modal' | ||
content: React.ReactNode | ||
onClose: () => void | ||
} | ||
|
||
// Todo: move these to action spec/core types | ||
interface PopOverDialogProps { | ||
type: 'popover' | ||
content: React.ReactNode | ||
onClose: () => void | ||
} | ||
|
||
export interface ActionDescription { | ||
label: string | ||
icon?: React.ReactNode | ||
disabled?: boolean | ||
shortcut?: string | ||
title?: string | ||
dialog?: ConfirmDialogProps | PopOverDialogProps | ModalDialogProps | ||
onHandle?: () => void | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
76 changes: 36 additions & 40 deletions
76
packages/@sanity/base/src/datastores/document/buffered-doc/createBufferedDocument.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,66 +1,62 @@ | ||
import {createObservableBufferedDocument} from './createObservableBufferedDocument' | ||
import {filter} from 'rxjs/operators' | ||
import {merge, Observable} from 'rxjs' | ||
import {ReconnectEvent} from '../types' | ||
import {Observable} from 'rxjs' | ||
import { | ||
CommitFunction, | ||
SnapshotEvent, | ||
CommittedEvent, | ||
DocumentMutationEvent, | ||
DocumentRebaseEvent, | ||
DocumentMutationEvent | ||
SnapshotEvent | ||
} from './types' | ||
import {ListenerEvent} from '../getPairListener' | ||
import {Mutation} from '../types' | ||
|
||
type BufferedDocumentEvent = | ||
| ReconnectEvent | ||
export type BufferedDocumentEvent = | ||
| SnapshotEvent | ||
| DocumentRebaseEvent | ||
| DocumentMutationEvent | ||
| CommittedEvent | ||
|
||
const prepare = id => document => { | ||
const {_id, _rev, _updatedAt, ...rest} = document | ||
return {_id: id, ...rest} | ||
} | ||
|
||
export interface BufferedDocumentWrapper { | ||
consistency$: Observable<boolean> | ||
events: Observable<BufferedDocumentEvent> | ||
patch: (patches) => void | ||
create: (document) => void | ||
createIfNotExists: (document) => void | ||
createOrReplace: (document) => void | ||
delete: () => void | ||
// helper functions | ||
patch: (patches) => Mutation[] | ||
create: (document) => Mutation | ||
createIfNotExists: (document) => Mutation | ||
createOrReplace: (document) => Mutation | ||
delete: () => Mutation | ||
|
||
mutate: (mutations: Mutation[]) => void | ||
commit: () => Observable<never> | ||
} | ||
|
||
function isReconnect(event: ListenerEvent): event is ReconnectEvent { | ||
return event.type === 'reconnect' | ||
} | ||
export const createBufferedDocument = ( | ||
documentId: string, | ||
serverEvents$: Observable<ListenerEvent>, | ||
doCommit: CommitFunction | ||
// consider naming it remoteEvent$ | ||
listenerEvent$: Observable<ListenerEvent>, | ||
commitMutations: CommitFunction | ||
): BufferedDocumentWrapper => { | ||
const bufferedDocument = createObservableBufferedDocument(serverEvents$, doCommit) | ||
const bufferedDocument = createObservableBufferedDocument(listenerEvent$, commitMutations) | ||
|
||
const prepareDoc = prepare(documentId) | ||
|
||
const reconnects$ = serverEvents$.pipe(filter(isReconnect)) | ||
const DELETE = {delete: {id: documentId}} | ||
|
||
return { | ||
events: merge(reconnects$, bufferedDocument.updates$), | ||
patch(patches) { | ||
bufferedDocument.addMutations(patches.map(patch => ({patch: {...patch, id: documentId}}))) | ||
}, | ||
create(document) { | ||
bufferedDocument.addMutation({ | ||
create: Object.assign({id: documentId}, document) | ||
}) | ||
}, | ||
createIfNotExists(document) { | ||
bufferedDocument.addMutation({createIfNotExists: document}) | ||
}, | ||
createOrReplace(document) { | ||
bufferedDocument.addMutation({createOrReplace: document}) | ||
}, | ||
delete() { | ||
bufferedDocument.addMutation({delete: {id: documentId}}) | ||
}, | ||
commit() { | ||
return bufferedDocument.commit() | ||
} | ||
events: bufferedDocument.updates$, | ||
consistency$: bufferedDocument.consistency$, | ||
patch: patches => patches.map(patch => ({patch: {...patch, id: documentId}})), | ||
create: document => ({create: prepareDoc(document)}), | ||
createIfNotExists: document => ({createIfNotExists: prepareDoc(document)}), | ||
createOrReplace: document => ({createOrReplace: prepareDoc(document)}), | ||
delete: () => DELETE, | ||
|
||
mutate: (mutations: Mutation[]) => bufferedDocument.addMutations(mutations), | ||
commit: () => bufferedDocument.commit() | ||
} | ||
} |
Oops, something went wrong.