feat: cursors should works with annotations

This commit is contained in:
cudr
2019-10-13 19:37:56 +03:00
parent 242a836ce8
commit 0ceb38bbfd
19 changed files with 214 additions and 186 deletions

View File

View File

@@ -22,7 +22,8 @@ class Connection {
socket: SocketIOClient.Socket
editor: ExtendedEditor
connectOpts: any
selection: any
annotationDataMixin: any
cursorAnnotationType: string
onConnect?: () => void
onDisconnect?: () => void
@@ -31,11 +32,16 @@ class Connection {
url,
connectOpts,
onConnect,
onDisconnect
onDisconnect,
cursorAnnotationType,
annotationDataMixin
}: ConnectionModel) {
this.url = url
this.editor = editor
this.connectOpts = connectOpts
this.cursorAnnotationType = cursorAnnotationType
this.annotationDataMixin = annotationDataMixin
this.onConnect = onConnect
this.onDisconnect = onDisconnect
@@ -58,9 +64,6 @@ class Connection {
const currentDoc = this.docSet.getDoc(this.docId)
const docNew = this.connection.receiveMsg(data)
console.log('current doc before updates', toJS(currentDoc))
console.log('new doc with remote updates!!', toJS(docNew))
if (!docNew) {
return
}
@@ -82,44 +85,12 @@ class Connection {
await Promise.resolve()
this.editor.remote = false
this.setCursors(docNew.cursors)
}
} catch (e) {
console.error(e)
}
}
setCursors = cursors => {
if (!cursors) return
// const {
// value: { annotations }
// } = this.editor
// const keyMap = {}
// console.log('cursors', cursors)
// this.editor.withoutSaving(() => {
// annotations.forEach(anno => {
// this.editor.removeAnnotation(anno)
// })
// Object.keys(cursors).forEach(key => {
// if (key !== this.socket.id && !keyMap[key]) {
// this.editor.addAnnotation(toJS(cursors[key]))
// }
// })
// })
console.log(
'!!!!VAL',
this.connectOpts.query.name,
this.editor.value.toJSON({ preserveAnnotations: true })
)
}
receiveSlateOps = (operations: Immutable.List<Operation>) => {
const doc = this.docSet.getDoc(this.docId)
const message = `change from ${this.socket.id}`
@@ -130,26 +101,18 @@ class Connection {
value: { selection }
} = this.editor
const annotationType = 'collaborative_selection'
const cursorData = {
id: this.socket.id,
selection,
// selectionOps: operations.filter(op => op.type === 'set_selection'),
annotationType
}
const withCursor = selection.isFocused ? setCursor : removeCursor
const changed = Automerge.change(doc, message, (d: any) =>
withCursor(
applySlateOps(d, cursorOpFilter(operations, annotationType)),
cursorData
applySlateOps(d, cursorOpFilter(operations, this.cursorAnnotationType)),
this.socket.id,
selection,
this.cursorAnnotationType,
this.annotationDataMixin
)
)
console.log('doc with annotations!!', toJS(changed))
this.docSet.setDoc(this.docId, changed)
}
@@ -174,7 +137,7 @@ class Connection {
}
connect = () => {
this.socket = io(this.url, this.connectOpts)
this.socket = io(this.url, { ...this.connectOpts })
this.socket.on('connect', () => {
this.connection = new Automerge.Connection(this.docSet, this.sendData)
@@ -190,6 +153,8 @@ class Connection {
disconnect = () => {
this.onDisconnect()
console.log('disconnect', this.socket)
this.connection && this.connection.close()
delete this.connection
@@ -202,6 +167,7 @@ class Connection {
this.onDisconnect()
this.socket.close()
// this.socket.destroy()
}
}

View File

@@ -4,7 +4,6 @@ import { KeyUtils } from 'slate'
import { hexGen } from '@slate-collaborative/bridge'
import Connection from './Connection'
import { ControllerProps } from './model'
class Controller extends Component<ControllerProps> {
@@ -15,7 +14,13 @@ class Controller extends Component<ControllerProps> {
}
componentDidMount() {
const { editor, url, connectOpts } = this.props
const {
editor,
url,
cursorAnnotationType,
annotationDataMixin,
connectOpts
} = this.props
KeyUtils.setGenerator(() => hexGen())
@@ -23,19 +28,11 @@ class Controller extends Component<ControllerProps> {
editor,
url,
connectOpts,
cursorAnnotationType,
annotationDataMixin,
onConnect: this.onConnect,
onDisconnect: this.onDisconnect
})
//@ts-ignore
if (!window.counter) {
//@ts-ignore
window.counter = 1
}
//@ts-ignore
window[`Editor_${counter}`] = editor
//@ts-ignore
window.counter += 1
}
componentWillUnmount() {
@@ -47,10 +44,10 @@ class Controller extends Component<ControllerProps> {
}
render() {
const { children, preloader } = this.props
const { children, renderPreloader } = this.props
const { preloading } = this.state
if (preloader && preloading) return preloader()
if (renderPreloader && preloading) return renderPreloader()
return children
}

View File

@@ -1,30 +1,34 @@
import { ReactNode } from 'react'
import onChange from './onChange'
import renderEditor from './renderEditor'
import renderAnnotation from './renderAnnotation'
import Connection from './Connection'
import { PluginOptions } from './model'
export interface PluginOptions {
url?: string
connectOpts?: SocketIOClient.ConnectOpts
preloader?: () => ReactNode
onConnect?: (connection: Connection) => void
onDisconnect?: (connection: Connection) => void
export const defaultOpts = {
url: 'http://localhost:9000',
cursorAnnotationType: 'collaborative_selection',
annotationDataMixin: {
name: 'an collaborator'
},
renderCursor: data => data.name,
cursorStyle: {
background: 'palevioletred'
},
caretStyle: {
background: 'palevioletred'
},
selectionStyle: {
background: 'rgba(233, 30, 99, 0.2)'
}
}
const defaultOpts = {
url: 'http://localhost:9000'
}
const plugin = (opts: PluginOptions = {}) => {
const plugin = (opts: PluginOptions = defaultOpts) => {
const options = { ...defaultOpts, ...opts }
return {
onChange: onChange(options),
renderEditor: renderEditor(options),
renderAnnotation
renderAnnotation: renderAnnotation(options)
}
}

View File

@@ -1,5 +1,7 @@
import { ReactNode } from 'react'
import CSS from 'csstype'
import { Editor, Controller, Value } from 'slate'
import { PluginOptions } from './index'
import Connection from './Connection'
interface FixedController extends Controller {
@@ -15,6 +17,7 @@ export interface ExtendedEditor extends Editor {
export interface ConnectionModel extends PluginOptions {
editor: ExtendedEditor
cursorAnnotationType: string
onConnect: () => void
onDisconnect: () => void
}
@@ -24,3 +27,18 @@ export interface ControllerProps extends PluginOptions {
url?: string
connectOpts?: SocketIOClient.ConnectOpts
}
export interface PluginOptions {
url?: string
connectOpts?: SocketIOClient.ConnectOpts
cursorAnnotationType?: string
caretStyle?: CSS.Properties
cursorStyle?: CSS.Properties
renderPreloader?: () => ReactNode
annotationDataMixin?: {
[key: string]: any
}
renderCursor?: (data: any) => ReactNode | string | any
onConnect?: (connection: Connection) => void
onDisconnect?: (connection: Connection) => void
}

View File

@@ -1,7 +1,7 @@
import React, { Fragment } from 'react'
const wrapStyles = {
backgroundColor: 'rgba(233, 30, 99, 0.2)',
background: 'rgba(233, 30, 99, 0.2)',
position: 'relative'
}
@@ -24,50 +24,53 @@ const caretStyleBase = {
userSelect: 'none',
height: '100%',
width: 2,
background: '#bf1b52'
}
background: 'palevioletred'
} as any
const renderAnnotation = (props, editor, next) => {
const renderAnnotation = ({
cursorAnnotationType,
renderCursor,
cursorStyle = {},
caretStyle = {},
selectionStyle = {}
}) => (props, editor, next) => {
const { children, annotation, attributes, node } = props
if (annotation.type !== cursorAnnotationType) return next()
const isBackward = annotation.data.get('isBackward')
const targetPath = annotation.data.get('targetPath')
const cursorText = renderCursor(annotation.data)
console.log(
'renderAnnotation',
annotation.toJS(),
props,
isBackward,
targetPath
)
const badgeStyles = { ...cursorStyleBase, left: isBackward ? '0%' : '100%' }
const caretStyles = { ...caretStyleBase, left: isBackward ? '0%' : '100%' }
const cursorStyles = {
...cursorStyleBase,
...cursorStyle,
left: isBackward ? '0%' : '100%'
}
const caretStyles = {
...caretStyleBase,
...caretStyle,
left: isBackward ? '0%' : '100%'
}
const { document } = editor.value
const targetNode = document.getNode(targetPath)
const isShowCursor = targetNode && targetNode.key === node.key
switch (annotation.type) {
case 'collaborative_selection':
return (
<span {...attributes} style={wrapStyles}>
{isShowCursor ? (
<Fragment>
<span contentEditable={false} style={badgeStyles}>
{annotation.key}
</span>
<span contentEditable={false} style={caretStyles} />
</Fragment>
) : null}
{children}
</span>
)
default:
return next()
}
return (
<span {...attributes} style={{ ...wrapStyles, ...selectionStyle }}>
{isShowCursor ? (
<Fragment>
<span contentEditable={false} style={cursorStyles}>
{cursorText}
</span>
<span contentEditable={false} style={caretStyles} />
</Fragment>
) : null}
{children}
</span>
)
}
export default renderAnnotation

View File

@@ -1,7 +1,6 @@
import React from 'react'
import { PluginOptions } from './index'
import { PluginOptions } from './model'
import Controller from './Controller'
const renderEditor = (opts: PluginOptions) => (