@textbus/collaborate 2.2.0-alpha.0 → 2.3.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundles/collaborate-cursor.d.ts +3 -1
- package/bundles/collaborate-cursor.js +3 -1
- package/bundles/collaborate.d.ts +60 -42
- package/bundles/collaborate.js +1 -1
- package/package.json +4 -4
- package/src/collaborate-cursor.ts +0 -289
- package/src/collaborate.ts +0 -732
- package/src/public-api.ts +0 -18
- package/src/unknown.component.ts +0 -22
- package/tsconfig-build.json +0 -28
|
@@ -1,289 +0,0 @@
|
|
|
1
|
-
import { Inject, Injectable, Optional } from '@tanbo/di'
|
|
2
|
-
import {
|
|
3
|
-
createElement,
|
|
4
|
-
VIEW_CONTAINER,
|
|
5
|
-
getLayoutRectByRange,
|
|
6
|
-
SelectionBridge
|
|
7
|
-
} from '@textbus/browser'
|
|
8
|
-
import { Selection, SelectionPaths, AbstractSelection, Scheduler, Rect } from '@textbus/core'
|
|
9
|
-
import { fromEvent, Subject, Subscription } from '@tanbo/stream'
|
|
10
|
-
|
|
11
|
-
export interface RemoteSelection {
|
|
12
|
-
color: string
|
|
13
|
-
username: string
|
|
14
|
-
paths: SelectionPaths
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface SelectionRect extends Rect {
|
|
18
|
-
color: string
|
|
19
|
-
username: string
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface RemoteSelectionCursor {
|
|
23
|
-
cursor: HTMLElement
|
|
24
|
-
anchor: HTMLElement
|
|
25
|
-
userTip: HTMLElement
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export abstract class CollaborateSelectionAwarenessDelegate {
|
|
29
|
-
abstract getRects(abstractSelection: AbstractSelection, nativeRange: Range): false | Rect[]
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
@Injectable()
|
|
33
|
-
export class CollaborateCursor {
|
|
34
|
-
private host = createElement('div', {
|
|
35
|
-
styles: {
|
|
36
|
-
position: 'absolute',
|
|
37
|
-
left: 0,
|
|
38
|
-
top: 0,
|
|
39
|
-
width: '100%',
|
|
40
|
-
height: '100%',
|
|
41
|
-
pointerEvents: 'none',
|
|
42
|
-
zIndex: 1
|
|
43
|
-
}
|
|
44
|
-
})
|
|
45
|
-
private canvasContainer = createElement('div', {
|
|
46
|
-
styles: {
|
|
47
|
-
position: 'absolute',
|
|
48
|
-
left: 0,
|
|
49
|
-
top: 0,
|
|
50
|
-
width: '100%',
|
|
51
|
-
height: '100%',
|
|
52
|
-
overflow: 'hidden'
|
|
53
|
-
}
|
|
54
|
-
})
|
|
55
|
-
private canvas = createElement('canvas', {
|
|
56
|
-
styles: {
|
|
57
|
-
position: 'absolute',
|
|
58
|
-
opacity: 0.5,
|
|
59
|
-
left: 0,
|
|
60
|
-
top: 0,
|
|
61
|
-
width: '100%',
|
|
62
|
-
height: document.documentElement.clientHeight + 'px',
|
|
63
|
-
pointerEvents: 'none',
|
|
64
|
-
}
|
|
65
|
-
}) as HTMLCanvasElement
|
|
66
|
-
private context = this.canvas.getContext('2d')!
|
|
67
|
-
private tooltips = createElement('div', {
|
|
68
|
-
styles: {
|
|
69
|
-
position: 'absolute',
|
|
70
|
-
left: 0,
|
|
71
|
-
top: 0,
|
|
72
|
-
width: '100%',
|
|
73
|
-
height: '100%',
|
|
74
|
-
pointerEvents: 'none',
|
|
75
|
-
fontSize: '12px',
|
|
76
|
-
zIndex: 10
|
|
77
|
-
}
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
private onRectsChange = new Subject<SelectionRect[]>()
|
|
81
|
-
|
|
82
|
-
private subscription = new Subscription()
|
|
83
|
-
private currentSelection: RemoteSelection[] = []
|
|
84
|
-
|
|
85
|
-
constructor(@Inject(VIEW_CONTAINER) private container: HTMLElement,
|
|
86
|
-
@Optional() private awarenessDelegate: CollaborateSelectionAwarenessDelegate,
|
|
87
|
-
private nativeSelection: SelectionBridge,
|
|
88
|
-
private scheduler: Scheduler,
|
|
89
|
-
private selection: Selection) {
|
|
90
|
-
this.canvasContainer.append(this.canvas)
|
|
91
|
-
this.host.append(this.canvasContainer, this.tooltips)
|
|
92
|
-
container.prepend(this.host)
|
|
93
|
-
this.subscription.add(this.onRectsChange.subscribe(rects => {
|
|
94
|
-
for (const rect of rects) {
|
|
95
|
-
this.context.fillStyle = rect.color
|
|
96
|
-
this.context.beginPath()
|
|
97
|
-
this.context.rect(rect.left, rect.top, rect.width, rect.height)
|
|
98
|
-
this.context.fill()
|
|
99
|
-
this.context.closePath()
|
|
100
|
-
}
|
|
101
|
-
}), fromEvent(window, 'resize').subscribe(() => {
|
|
102
|
-
this.canvas.style.height = document.documentElement.clientHeight + 'px'
|
|
103
|
-
this.refresh()
|
|
104
|
-
}), this.scheduler.onDocChanged.subscribe(() => {
|
|
105
|
-
this.refresh()
|
|
106
|
-
}))
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
refresh() {
|
|
110
|
-
this.draw(this.currentSelection)
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
destroy() {
|
|
114
|
-
this.subscription.unsubscribe()
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
draw(paths: RemoteSelection[]) {
|
|
118
|
-
this.currentSelection = paths
|
|
119
|
-
const containerRect = this.container.getBoundingClientRect()
|
|
120
|
-
this.canvas.style.top = containerRect.top * -1 + 'px'
|
|
121
|
-
this.canvas.width = this.canvas.offsetWidth
|
|
122
|
-
this.canvas.height = this.canvas.offsetHeight
|
|
123
|
-
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height)
|
|
124
|
-
|
|
125
|
-
const users: SelectionRect[] = []
|
|
126
|
-
|
|
127
|
-
paths.filter(i => {
|
|
128
|
-
return i.paths.anchor.length && i.paths.focus.length
|
|
129
|
-
}).forEach(item => {
|
|
130
|
-
const anchorPaths = [...item.paths.anchor]
|
|
131
|
-
const focusPaths = [...item.paths.focus]
|
|
132
|
-
const anchorOffset = anchorPaths.pop()!
|
|
133
|
-
const anchorSlot = this.selection.findSlotByPaths(anchorPaths)
|
|
134
|
-
const focusOffset = focusPaths.pop()!
|
|
135
|
-
const focusSlot = this.selection.findSlotByPaths(focusPaths)
|
|
136
|
-
if (!anchorSlot || !focusSlot) {
|
|
137
|
-
return
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const { focus, anchor } = this.nativeSelection.getPositionByRange({
|
|
141
|
-
focusOffset,
|
|
142
|
-
anchorOffset,
|
|
143
|
-
focusSlot,
|
|
144
|
-
anchorSlot
|
|
145
|
-
})
|
|
146
|
-
if (!focus || !anchor) {
|
|
147
|
-
return
|
|
148
|
-
}
|
|
149
|
-
const nativeRange = document.createRange()
|
|
150
|
-
nativeRange.setStart(anchor.node, anchor.offset)
|
|
151
|
-
nativeRange.setEnd(focus.node, focus.offset)
|
|
152
|
-
if ((anchor.node !== focus.node || anchor.offset !== focus.offset) && nativeRange.collapsed) {
|
|
153
|
-
nativeRange.setStart(focus.node, focus.offset)
|
|
154
|
-
nativeRange.setEnd(anchor.node, anchor.offset)
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
let rects: Rect[] | DOMRectList | false = false
|
|
158
|
-
if (this.awarenessDelegate) {
|
|
159
|
-
rects = this.awarenessDelegate.getRects({
|
|
160
|
-
focusOffset,
|
|
161
|
-
anchorOffset,
|
|
162
|
-
focusSlot,
|
|
163
|
-
anchorSlot
|
|
164
|
-
}, nativeRange)
|
|
165
|
-
}
|
|
166
|
-
if (!rects) {
|
|
167
|
-
rects = nativeRange.getClientRects()
|
|
168
|
-
}
|
|
169
|
-
const selectionRects: SelectionRect[] = []
|
|
170
|
-
for (let i = rects.length - 1; i >= 0; i--) {
|
|
171
|
-
const rect = rects[i]
|
|
172
|
-
selectionRects.push({
|
|
173
|
-
color: item.color,
|
|
174
|
-
username: item.username,
|
|
175
|
-
left: rect.left - containerRect.left,
|
|
176
|
-
top: rect.top,
|
|
177
|
-
width: rect.width,
|
|
178
|
-
height: rect.height,
|
|
179
|
-
})
|
|
180
|
-
}
|
|
181
|
-
this.onRectsChange.next(selectionRects)
|
|
182
|
-
|
|
183
|
-
const cursorRange = nativeRange.cloneRange()
|
|
184
|
-
cursorRange.setStart(focus.node, focus.offset)
|
|
185
|
-
cursorRange.collapse(true)
|
|
186
|
-
|
|
187
|
-
const cursorRect = getLayoutRectByRange(cursorRange)
|
|
188
|
-
|
|
189
|
-
const rect: SelectionRect = {
|
|
190
|
-
username: item.username,
|
|
191
|
-
color: item.color,
|
|
192
|
-
left: cursorRect.left - containerRect.left,
|
|
193
|
-
top: cursorRect.top - containerRect.top,
|
|
194
|
-
width: 2,
|
|
195
|
-
height: cursorRect.height
|
|
196
|
-
}
|
|
197
|
-
if (rect.left < 0 || rect.top < 0 || rect.left > containerRect.width) {
|
|
198
|
-
return
|
|
199
|
-
}
|
|
200
|
-
users.push(rect)
|
|
201
|
-
})
|
|
202
|
-
this.drawUserCursor(users)
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
private drawUserCursor(rects: SelectionRect[]) {
|
|
206
|
-
for (let i = 0; i < rects.length; i++) {
|
|
207
|
-
const rect = rects[i]
|
|
208
|
-
const { cursor, userTip, anchor } = this.getUserCursor(i)
|
|
209
|
-
Object.assign(cursor.style, {
|
|
210
|
-
left: rect.left + 'px',
|
|
211
|
-
top: rect.top + 'px',
|
|
212
|
-
width: rect.width + 'px',
|
|
213
|
-
height: rect.height + 'px',
|
|
214
|
-
background: rect.color,
|
|
215
|
-
display: 'block'
|
|
216
|
-
})
|
|
217
|
-
anchor.style.background = rect.color
|
|
218
|
-
userTip.innerText = rect.username
|
|
219
|
-
userTip.style.background = rect.color
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
for (let i = rects.length; i < this.tooltips.children.length; i++) {
|
|
223
|
-
this.tooltips.removeChild(this.tooltips.children[i])
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
private getUserCursor(index: number): RemoteSelectionCursor {
|
|
228
|
-
let child: HTMLElement = this.tooltips.children[index] as HTMLElement
|
|
229
|
-
if (child) {
|
|
230
|
-
const anchor = child.children[0] as HTMLElement
|
|
231
|
-
return {
|
|
232
|
-
cursor: child,
|
|
233
|
-
anchor,
|
|
234
|
-
userTip: anchor.children[0] as HTMLElement
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
const userTip = createElement('span', {
|
|
238
|
-
styles: {
|
|
239
|
-
position: 'absolute',
|
|
240
|
-
display: 'none',
|
|
241
|
-
left: '50%',
|
|
242
|
-
transform: 'translateX(-50%)',
|
|
243
|
-
marginBottom: '2px',
|
|
244
|
-
bottom: '100%',
|
|
245
|
-
whiteSpace: 'nowrap',
|
|
246
|
-
color: '#fff',
|
|
247
|
-
boxShadow: '0 1px 2px rgba(0,0,0,.1)',
|
|
248
|
-
borderRadius: '3px',
|
|
249
|
-
padding: '3px 5px',
|
|
250
|
-
pointerEvents: 'none',
|
|
251
|
-
}
|
|
252
|
-
})
|
|
253
|
-
|
|
254
|
-
const anchor = createElement('span', {
|
|
255
|
-
styles: {
|
|
256
|
-
position: 'absolute',
|
|
257
|
-
top: '-2px',
|
|
258
|
-
left: '-2px',
|
|
259
|
-
width: '6px',
|
|
260
|
-
height: '6px',
|
|
261
|
-
pointerEvents: 'auto',
|
|
262
|
-
pointer: 'cursor',
|
|
263
|
-
},
|
|
264
|
-
children: [userTip],
|
|
265
|
-
on: {
|
|
266
|
-
mouseenter() {
|
|
267
|
-
userTip.style.display = 'block'
|
|
268
|
-
},
|
|
269
|
-
mouseleave() {
|
|
270
|
-
userTip.style.display = 'none'
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
})
|
|
274
|
-
child = createElement('span', {
|
|
275
|
-
styles: {
|
|
276
|
-
position: 'absolute',
|
|
277
|
-
},
|
|
278
|
-
children: [
|
|
279
|
-
anchor
|
|
280
|
-
]
|
|
281
|
-
})
|
|
282
|
-
this.tooltips.append(child)
|
|
283
|
-
return {
|
|
284
|
-
cursor: child,
|
|
285
|
-
anchor,
|
|
286
|
-
userTip
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
}
|