@textbus/collaborate 2.2.0-alpha.0 → 2.3.0-alpha.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- }