feat: extract cursor component

This commit is contained in:
cudr 2019-10-14 00:49:28 +03:00
parent 0ceb38bbfd
commit 21a4a7d99a
14 changed files with 105 additions and 104 deletions

View File

@ -118,6 +118,8 @@ class Connection {
this.connections[id].receiveMsg(data)
this.saveDoc(name)
this.garbageCursors(name)
} catch (e) {
console.log(e)
}

View File

@ -1,4 +1,4 @@
import { Operation, SyncDoc } from '../model'
import { Operation, SyncDoc } from '../model/index'
export const addAnnotation = (doc: SyncDoc, op: Operation): SyncDoc => {
console.log('addAnnotation!!!', op.toJS())

View File

@ -21,7 +21,10 @@ export const applyOperation = (doc: SyncDoc, op: Operation): SyncDoc => {
try {
const applyOp = opType[op.type]
if (!applyOp) throw new TypeError('Unsupported operation type!')
if (!applyOp) {
console.log('operation', op.toJS())
throw new TypeError(`Unsupported operation type: ${op.type}!`)
}
return applyOp(doc, op)
} catch (e) {

View File

@ -30,9 +30,7 @@ const toSlateOp = (ops: Automerge.Diff[], doc) => {
[]
])
const res = defer.flatMap(op => op(tempTree, doc)).filter(op => op)
return res
return defer.flatMap(op => op(tempTree, doc)).filter(op => op)
}
export { toSlateOp }

View File

@ -1,5 +1,5 @@
import { Operation, Selection } from 'slate'
import { List } from 'immutable'
import * as Immutable from 'immutable'
import merge from 'lodash/merge'
import { toJS } from '../utils'
@ -51,7 +51,7 @@ export const removeCursor = (doc: SyncDoc, key: CursorKey) => {
return doc
}
export const cursorOpFilter = (ops: List<Operation>, type: string) =>
export const cursorOpFilter = (ops: Immutable.List<Operation>, type: string) =>
ops.filter(op => {
if (op.type === 'set_annotation') {
return !(

View File

@ -0,0 +1,47 @@
import React, { Fragment } from 'react'
const cursorStyleBase = {
position: 'absolute',
top: -2,
pointerEvents: 'none',
userSelect: 'none',
transform: 'translateY(-100%)',
fontSize: 10,
color: 'white',
background: 'palevioletred',
whiteSpace: 'nowrap'
} as any
const caretStyleBase = {
position: 'absolute',
top: 0,
pointerEvents: 'none',
userSelect: 'none',
height: '100%',
width: 2,
background: 'palevioletred'
} as any
const Cursor = ({ color, isBackward, name }) => {
const cursorStyles = {
...cursorStyleBase,
background: color,
left: isBackward ? '0%' : '100%'
}
const caretStyles = {
...caretStyleBase,
background: color,
left: isBackward ? '0%' : '100%'
}
return (
<Fragment>
<span contentEditable={false} style={cursorStyles}>
{name}
</span>
<span contentEditable={false} style={caretStyles} />
</Fragment>
)
}
export default Cursor

View File

@ -2,23 +2,18 @@ import onChange from './onChange'
import renderEditor from './renderEditor'
import renderAnnotation from './renderAnnotation'
import renderCursor from './renderCursor'
import { PluginOptions } from './model'
export const defaultOpts = {
url: 'http://localhost:9000',
cursorAnnotationType: 'collaborative_selection',
renderCursor,
annotationDataMixin: {
name: 'an collaborator'
},
renderCursor: data => data.name,
cursorStyle: {
background: 'palevioletred'
},
caretStyle: {
background: 'palevioletred'
},
selectionStyle: {
background: 'rgba(233, 30, 99, 0.2)'
name: 'an collaborator name',
color: 'palevioletred',
alphaColor: 'rgba(233, 30, 99, 0.2)'
}
}

View File

@ -1,9 +1,12 @@
import { ReactNode } from 'react'
import CSS from 'csstype'
import { Editor, Controller, Value } from 'slate'
import Connection from './Connection'
type Data = {
[key: string]: any
}
interface FixedController extends Controller {
setValue: (value: Value) => void
}
@ -32,13 +35,9 @@ export interface PluginOptions {
url?: string
connectOpts?: SocketIOClient.ConnectOpts
cursorAnnotationType?: string
caretStyle?: CSS.Properties
cursorStyle?: CSS.Properties
annotationDataMixin?: Data
renderPreloader?: () => ReactNode
annotationDataMixin?: {
[key: string]: any
}
renderCursor?: (data: any) => ReactNode | string | any
renderCursor?: (data: Data) => ReactNode | any
onConnect?: (connection: Connection) => void
onDisconnect?: (connection: Connection) => void
}

View File

@ -1,73 +1,28 @@
import React, { Fragment } from 'react'
import React from 'react'
const wrapStyles = {
background: 'rgba(233, 30, 99, 0.2)',
position: 'relative'
}
const cursorStyleBase = {
position: 'absolute',
top: -2,
pointerEvents: 'none',
userSelect: 'none',
transform: 'translateY(-100%)',
fontSize: 10,
color: 'white',
background: 'palevioletred',
whiteSpace: 'nowrap'
} as any
const caretStyleBase = {
position: 'absolute',
top: 0,
pointerEvents: 'none',
userSelect: 'none',
height: '100%',
width: 2,
background: 'palevioletred'
} as any
const renderAnnotation = ({
cursorAnnotationType,
renderCursor,
cursorStyle = {},
caretStyle = {},
selectionStyle = {}
}) => (props, editor, next) => {
const renderAnnotation = ({ cursorAnnotationType, renderCursor }) => (
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)
const cursorStyles = {
...cursorStyleBase,
...cursorStyle,
left: isBackward ? '0%' : '100%'
}
const caretStyles = {
...caretStyleBase,
...caretStyle,
left: isBackward ? '0%' : '100%'
}
const data = annotation.data.toJS()
const { targetPath, alphaColor } = data
const { document } = editor.value
const targetNode = document.getNode(targetPath)
const isShowCursor = targetNode && targetNode.key === node.key
const showCursor = targetNode && targetNode.key === node.key
return (
<span {...attributes} style={{ ...wrapStyles, ...selectionStyle }}>
{isShowCursor ? (
<Fragment>
<span contentEditable={false} style={cursorStyles}>
{cursorText}
</span>
<span contentEditable={false} style={caretStyles} />
</Fragment>
) : null}
<span
{...attributes}
style={{ position: 'relative', background: alphaColor }}
>
{showCursor ? renderCursor(data) : null}
{children}
</span>
)

View File

@ -0,0 +1,7 @@
import React from 'react'
import Cursor from './Cursor'
const renderCursor = data => <Cursor {...data} />
export default renderCursor

View File

@ -16,6 +16,7 @@
"concurrently": "^4.1.2",
"faker": "^4.1.0",
"lodash": "^4.17.15",
"nodemon": "^1.19.2",
"randomcolor": "^0.5.4",
"react": "^16.9.0",
"react-dom": "^16.9.0",
@ -26,10 +27,9 @@
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"dev": "concurrently \"yarn start\" \"yarn serve\"",
"serve": "nodemon --watch ../backend/lib --inspect server.js",
"test": "react-scripts test",
"eject": "react-scripts eject"
"serve": "nodemon --watch ../backend/lib --inspect server.js"
},
"eslintConfig": {
"extends": "react-app"
@ -45,8 +45,5 @@
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"nodemon": "^1.19.2"
}
}

View File

@ -19,9 +19,11 @@ class App extends Component<{}, { rooms: string[] }> {
return (
<Container>
<Panel>
<AddButton type="button" onClick={this.addRoom}>
Add Room
</AddButton>
</Panel>
{rooms.map(room => (
<Room key={room} slug={room} removeRoom={this.removeRoom(room)} />
))}
@ -46,6 +48,10 @@ export default App
const Container = styled.div``
const Panel = styled.div`
display: flex;
`
const Button = styled.button`
padding: 6px 14px;
display: block;

View File

@ -45,18 +45,10 @@ class Client extends Component<ClienProps> {
}
},
annotationDataMixin: {
name: this.props.name
name: this.props.name,
color,
alphaColor: color.slice(0, -2) + '0.2)'
},
cursorStyle: {
background: color
},
caretStyle: {
background: color
},
selectionStyle: {
background: color.slice(0, -2) + '0.2)'
},
renderCursor: data => data.get('name'),
// renderPreloader: () => <div>PRELOADER!!!!!!</div>,
onConnect: this.onConnect,
onDisconnect: this.onDisconnect