Browse Source

feat: update

wxyyxc1992 5 years ago
parent
commit
9b18ca2c44

+ 1
- 0
src/assets/drag.svg View File

1
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1555470391343" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2050" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" id="drag-handler"><defs><style type="text/css"></style></defs><path d="M298.666667 810.666667v-85.333334h85.333333v85.333334H298.666667m170.666666 0v-85.333334h85.333334v85.333334h-85.333334m170.666667 0v-85.333334h85.333333v85.333334h-85.333333m-341.333333-170.666667v-85.333333h85.333333v85.333333H298.666667m170.666666 0v-85.333333h85.333334v85.333333h-85.333334m170.666667 0v-85.333333h85.333333v85.333333h-85.333333m-341.333333-170.666667V384h85.333333v85.333333H298.666667m170.666666 0V384h85.333334v85.333333h-85.333334m170.666667 0V384h85.333333v85.333333h-85.333333M298.666667 298.666667V213.333333h85.333333v85.333334H298.666667m170.666666 0V213.333333h85.333334v85.333334h-85.333334m170.666667 0V213.333333h85.333333v85.333334h-85.333333z" fill="" p-id="2051"></path></svg>

+ 0
- 8
src/drawboard/Baseboard/index.ts View File

71
     this.boardHolder.style.top = this.targetRect.top + 'px';
71
     this.boardHolder.style.top = this.targetRect.top + 'px';
72
     this.boardHolder.style.left = this.targetRect.left + 'px';
72
     this.boardHolder.style.left = this.targetRect.left + 'px';
73
   };
73
   };
74
-
75
-  protected initEmbeddingToolbar = () => {};
76
-
77
-  protected positionEmbeddingToolbar = () => {};
78
-
79
-  protected initFloatingToolbar = () => {};
80
-
81
-  protected positionFloatingToolbar = () => {};
82
 }
74
 }

+ 0
- 47
src/drawboard/Drawboard/index.less View File

19
   fill: #ff8080;
19
   fill: #ff8080;
20
 }
20
 }
21
 
21
 
22
-.fc-whiteboard-toolbar {
23
-  background-color: #cccccc;
24
-  padding: 0px 5px;
25
-  margin: 0px;
26
-  border-top-left-radius: 10px;
27
-  border-top-right-radius: 10px;
28
-
29
-  display: grid;
30
-  grid-template-columns: repeat(20, auto);
31
-}
32
-
33
-.fc-whiteboard-toolbar-button,
34
-.fc-whiteboard-toolbar-logo a {
35
-  display: inline-block;
36
-  margin: 2px;
37
-  padding: 3px;
38
-  cursor: pointer;
39
-  width: 20px;
40
-  height: 20px;
41
-  border-radius: 2px;
42
-  border-bottom: transparent solid 1px;
43
-  border-right: transparent solid 1px;
44
-
45
-  fill: #333333;
46
-
47
-  display: grid;
48
-  align-items: center;
49
-  justify-items: center;
50
-}
51
-
52
-.fc-whiteboard-toolbar-separator {
53
-  margin: 5px 5px;
54
-  border: 1px solid #dddddd;
55
-}
56
-
57
-.fc-whiteboard-toolbar-button:hover,
58
-.fc-whiteboard-toolbar-logo a:hover {
59
-  background-color: #eeeeee;
60
-  background: radial-gradient(#eeeeee, #cccccc);
61
-
62
-  fill: #ff8080;
63
-}
64
-
65
-.fc-whiteboard-toolbar-button svg {
66
-  height: 16px;
67
-}
68
-
69
 .fc-whiteboard-text-editor {
22
 .fc-whiteboard-text-editor {
70
   position: fixed;
23
   position: fixed;
71
   z-index: 20000;
24
   z-index: 20000;

+ 42
- 7
src/drawboard/Drawboard/index.ts View File

29
   }
29
   }
30
   activeMarker: BaseMarker | null;
30
   activeMarker: BaseMarker | null;
31
 
31
 
32
+  toolbarItems: ToolbarItem[];
33
+
32
   toolbar: Toolbar;
34
   toolbar: Toolbar;
33
-  toolbars: ToolbarItem[];
34
   toolbarUI: HTMLElement;
35
   toolbarUI: HTMLElement;
35
 
36
 
36
   /** 回调 */
37
   /** 回调 */
40
 
41
 
41
   constructor(
42
   constructor(
42
     source: Source,
43
     source: Source,
43
-    { page, zIndex, onChange }: { page?: WhitePage; zIndex?: number; onChange?: onSyncFunc } = {}
44
+    {
45
+      page,
46
+      zIndex,
47
+      extraToolbarItems,
48
+      onChange
49
+    }: Partial<Drawboard> & { extraToolbarItems?: ToolbarItem[] } = {}
44
   ) {
50
   ) {
45
     super(source);
51
     super(source);
46
 
52
 
54
 
60
 
55
     this.markers = [];
61
     this.markers = [];
56
     this.activeMarker = null;
62
     this.activeMarker = null;
57
-    this.toolbars = getToolbars(page);
63
+
64
+    const toolbarItems = getToolbars(page);
65
+
66
+    if (extraToolbarItems) {
67
+      toolbarItems.push(...extraToolbarItems);
68
+    }
69
+
70
+    this.toolbarItems = toolbarItems;
58
 
71
 
59
     if (onChange) {
72
     if (onChange) {
60
       this.onChange = onChange;
73
       this.onChange = onChange;
80
 
93
 
81
     window.addEventListener('resize', this.adjustUI);
94
     window.addEventListener('resize', this.adjustUI);
82
 
95
 
83
-    if (this.page.mode === 'master') {
96
+    if ((this.page && this.page.mode === 'master') || !this.page) {
84
       this.showUI();
97
       this.showUI();
85
     }
98
     }
86
   };
99
   };
263
   };
276
   };
264
 
277
 
265
   private showUI = () => {
278
   private showUI = () => {
266
-    this.toolbar = new Toolbar(this.toolbars, this.toolbarClick);
279
+    this.toolbar = new Toolbar(this.toolbarItems, this.toolbarClick);
267
     this.toolbar.zIndex = this.zIndex;
280
     this.toolbar.zIndex = this.zIndex;
268
 
281
 
269
-    this.toolbarUI = this.toolbar.getUI();
282
+    this.toolbarUI = this.toolbar.getUI(this);
270
 
283
 
271
     document.body.appendChild(this.toolbarUI);
284
     document.body.appendChild(this.toolbarUI);
272
     this.toolbarUI.style.position = 'absolute';
285
     this.toolbarUI.style.position = 'absolute';
273
 
286
 
274
     this.positionToolbar();
287
     this.positionToolbar();
288
+
289
+    // 处理元素的拖拽事件
290
+    this.toolbar.toolbarButtons.forEach(button => {
291
+      if (button.toolbarItem.draggable) {
292
+        button.container.draggable = true;
293
+        button.container.ondragstart = ev => {
294
+          if (ev) {
295
+            ev.dataTransfer!.setData('id', button.id);
296
+          }
297
+        };
298
+      }
299
+    });
300
+
301
+    this.boardCanvas.ondragover = ev => {
302
+      ev.preventDefault();
303
+    };
304
+    this.boardCanvas.ondrop = ev => {
305
+      console.log(ev);
306
+    };
275
   };
307
   };
276
 
308
 
277
   private setStyles = () => {
309
   private setStyles = () => {
329
     this.boardCanvas.appendChild(editorStyleSheet);
361
     this.boardCanvas.appendChild(editorStyleSheet);
330
   };
362
   };
331
 
363
 
364
+  /** 处理 Toolbar 的点击事件 */
332
   private toolbarClick = (ev: MouseEvent, toolbarItem: ToolbarItem) => {
365
   private toolbarClick = (ev: MouseEvent, toolbarItem: ToolbarItem) => {
333
-    if (toolbarItem.markerType) {
366
+    if (toolbarItem.onClick) {
367
+      toolbarItem.onClick();
368
+    } else if (toolbarItem.markerType) {
334
       this.addMarker(toolbarItem.markerType);
369
       this.addMarker(toolbarItem.markerType);
335
     } else {
370
     } else {
336
       // command button
371
       // command button

+ 7
- 2
src/index.ts View File

1
-export { EventHub } from './event/EventHub';
2
 export { Drawboard } from './drawboard/Drawboard';
1
 export { Drawboard } from './drawboard/Drawboard';
3
-export { Whiteboard } from './whiteboard/Whiteboard';
2
+export { separatorToolbarItem, closeToolbarItem } from './toolbar/toolbar-items';
3
+
4
 export { Mode } from './utils/types';
4
 export { Mode } from './utils/types';
5
+export { SyncEvent } from './event/SyncEvent';
6
+export { EventHub } from './event/EventHub';
7
+export { Whiteboard } from './whiteboard/Whiteboard';
8
+export { MirrorWhiteboard } from './whiteboard/MirrorWhiteboard';
9
+export { ReplayWhiteboard } from './whiteboard/ReplayWhiteboard';

+ 3
- 3
src/markers/ArrowMarker/index.ts View File

9
   public static createMarker = (page?: WhitePage): LinearMarker => {
9
   public static createMarker = (page?: WhitePage): LinearMarker => {
10
     const marker = new ArrowMarker();
10
     const marker = new ArrowMarker();
11
     marker.page = page;
11
     marker.page = page;
12
-    marker.setup();
12
+    marker.init();
13
     return marker;
13
     return marker;
14
   };
14
   };
15
 
15
 
16
   private readonly ARROW_SIZE = 6;
16
   private readonly ARROW_SIZE = 6;
17
 
17
 
18
-  protected setup() {
19
-    super.setup();
18
+  protected init() {
19
+    super.init();
20
     SvgHelper.setAttributes(this.visual, [['class', 'arrow-marker']]);
20
     SvgHelper.setAttributes(this.visual, [['class', 'arrow-marker']]);
21
 
21
 
22
     const tip = SvgHelper.createPolygon(
22
     const tip = SvgHelper.createPolygon(

+ 17
- 69
src/markers/BaseMarker/index.ts View File

6
 import { SvgHelper } from '../../renderer/SvgHelper';
6
 import { SvgHelper } from '../../renderer/SvgHelper';
7
 import { MarkerSnap } from '../../whiteboard/AbstractWhiteboard/snap';
7
 import { MarkerSnap } from '../../whiteboard/AbstractWhiteboard/snap';
8
 import { Drawboard } from '../../drawboard/Drawboard/index';
8
 import { Drawboard } from '../../drawboard/Drawboard/index';
9
+import { DomEventAware } from '../../renderer/DomEventAware/index';
10
+import { isNil } from '../../utils/types';
9
 
11
 
10
-export class BaseMarker {
12
+export class BaseMarker extends DomEventAware {
11
   id: string = uuid();
13
   id: string = uuid();
12
   type: MarkerType = 'base';
14
   type: MarkerType = 'base';
13
   // 归属的 WhitePage
15
   // 归属的 WhitePage
20
   public static createMarker = (page?: WhitePage): BaseMarker => {
22
   public static createMarker = (page?: WhitePage): BaseMarker => {
21
     const marker = new BaseMarker();
23
     const marker = new BaseMarker();
22
     marker.page = page;
24
     marker.page = page;
23
-    marker.setup();
25
+    marker.init();
24
     return marker;
26
     return marker;
25
   };
27
   };
26
 
28
 
31
 
33
 
32
   public defs: SVGElement[] = [];
34
   public defs: SVGElement[] = [];
33
 
35
 
34
-  x: number = 0;
35
-  y: number = 0;
36
   width: number = 200;
36
   width: number = 200;
37
   height: number = 50;
37
   height: number = 50;
38
 
38
 
39
-  protected isActive: boolean = true;
40
-  protected isDragging: boolean = false;
41
-  protected isResizing: boolean = false;
42
-
43
-  protected previousMouseX: number = 0;
44
-  protected previousMouseY: number = 0;
39
+  isActive: boolean = true;
40
+  isDragging: boolean = false;
41
+  isResizing: boolean = false;
45
 
42
 
46
   public reactToManipulation(
43
   public reactToManipulation(
47
     type: EventType,
44
     type: EventType,
48
     { dx, dy, pos }: { dx?: number; dy?: number; pos?: PositionType } = {}
45
     { dx, dy, pos }: { dx?: number; dy?: number; pos?: PositionType } = {}
49
   ) {
46
   ) {
50
     if (type === 'moveMarker') {
47
     if (type === 'moveMarker') {
51
-      if (!dx || !dy) {
48
+      if (isNil(dx) || isNil(dy)) {
52
         return;
49
         return;
53
       }
50
       }
54
 
51
 
55
-      this.move(dx, dy);
52
+      this.move(dx!, dy!);
56
     }
53
     }
57
 
54
 
58
     if (type === 'resizeMarker') {
55
     if (type === 'resizeMarker') {
59
-      if (!dx || !dy) {
56
+      if (isNil(dx) || isNil(dy)) {
60
         return;
57
         return;
61
       }
58
       }
62
 
59
 
63
-      this.resizeByEvent(dx, dy, pos);
60
+      this.resizeByEvent(dx!, dy!, pos);
64
     }
61
     }
65
   }
62
   }
66
 
63
 
163
     this.y = y;
160
     this.y = y;
164
   };
161
   };
165
 
162
 
166
-  /** Init */
167
-  protected setup() {
163
+  /** Init base marker */
164
+  protected init() {
168
     this.visual = SvgHelper.createGroup();
165
     this.visual = SvgHelper.createGroup();
169
     // translate
166
     // translate
170
     this.visual.transform.baseVal.appendItem(SvgHelper.createTransform());
167
     this.visual.transform.baseVal.appendItem(SvgHelper.createTransform());
171
 
168
 
172
-    this.visual.addEventListener('mousedown', this.mouseDown);
173
-    this.visual.addEventListener('mouseup', this.mouseUp);
174
-    this.visual.addEventListener('mousemove', this.mouseMove);
175
-
176
-    this.visual.addEventListener('touchstart', this.onTouch, { passive: false });
177
-    this.visual.addEventListener('touchend', this.onTouch, { passive: false });
178
-    this.visual.addEventListener('touchmove', this.onTouch, { passive: false });
179
-
169
+    super.init(this.visual);
180
     this.renderVisual = SvgHelper.createGroup([['class', 'render-visual']]);
170
     this.renderVisual = SvgHelper.createGroup([['class', 'render-visual']]);
181
     this.visual.appendChild(this.renderVisual);
171
     this.visual.appendChild(this.renderVisual);
182
   }
172
   }
189
     this.renderVisual.appendChild(el);
179
     this.renderVisual.appendChild(el);
190
   };
180
   };
191
 
181
 
192
-  /** 截获 Touch 事件,并且转发为 Mouse 事件 */
193
-  protected onTouch(ev: TouchEvent) {
194
-    ev.preventDefault();
195
-    const newEvt = document.createEvent('MouseEvents');
196
-    const touch = ev.changedTouches[0];
197
-    let type = null;
198
-
199
-    switch (ev.type) {
200
-      case 'touchstart':
201
-        type = 'mousedown';
202
-        break;
203
-      case 'touchmove':
204
-        type = 'mousemove';
205
-        break;
206
-      case 'touchend':
207
-        type = 'mouseup';
208
-        break;
209
-      default:
210
-        break;
211
-    }
212
-
213
-    newEvt.initMouseEvent(
214
-      type!,
215
-      true,
216
-      true,
217
-      window,
218
-      0,
219
-      touch.screenX,
220
-      touch.screenY,
221
-      touch.clientX,
222
-      touch.clientY,
223
-      ev.ctrlKey,
224
-      ev.altKey,
225
-      ev.shiftKey,
226
-      ev.metaKey,
227
-      0,
228
-      null
229
-    );
230
-
231
-    ev.target!.dispatchEvent(newEvt);
232
-  }
233
-
234
-  private mouseDown = (ev: MouseEvent) => {
182
+  protected onMouseDown = (ev: MouseEvent) => {
235
     ev.stopPropagation();
183
     ev.stopPropagation();
236
 
184
 
237
     if (this.page && this.page.mode === 'mirror') {
185
     if (this.page && this.page.mode === 'mirror') {
244
     this.previousMouseY = ev.screenY;
192
     this.previousMouseY = ev.screenY;
245
   };
193
   };
246
 
194
 
247
-  private mouseUp = (ev: MouseEvent) => {
195
+  protected onMouseUp = (ev: MouseEvent) => {
248
     ev.stopPropagation();
196
     ev.stopPropagation();
249
     this.endManipulation();
197
     this.endManipulation();
250
   };
198
   };
251
 
199
 
252
-  private mouseMove = (ev: MouseEvent) => {
200
+  protected onMouseMove = (ev: MouseEvent) => {
253
     ev.stopPropagation();
201
     ev.stopPropagation();
254
     this.manipulate(ev);
202
     this.manipulate(ev);
255
   };
203
   };

+ 3
- 3
src/markers/CoverMarker/index.ts View File

9
   public static createMarker = (page?: WhitePage): RectBaseMarker => {
9
   public static createMarker = (page?: WhitePage): RectBaseMarker => {
10
     const marker = new CoverMarker();
10
     const marker = new CoverMarker();
11
     marker.page = page;
11
     marker.page = page;
12
-    marker.setup();
12
+    marker.init();
13
     return marker;
13
     return marker;
14
   };
14
   };
15
 
15
 
16
-  protected setup() {
17
-    super.setup();
16
+  protected init() {
17
+    super.init();
18
     SvgHelper.setAttributes(this.visual, [['class', 'cover-marker']]);
18
     SvgHelper.setAttributes(this.visual, [['class', 'cover-marker']]);
19
   }
19
   }
20
 }
20
 }

+ 3
- 3
src/markers/HighlightMarker/index.ts View File

9
   public static createMarker = (page?: WhitePage): RectBaseMarker => {
9
   public static createMarker = (page?: WhitePage): RectBaseMarker => {
10
     const marker = new HighlightMarker();
10
     const marker = new HighlightMarker();
11
     marker.page = page;
11
     marker.page = page;
12
-    marker.setup();
12
+    marker.init();
13
     return marker;
13
     return marker;
14
   };
14
   };
15
 
15
 
16
-  protected setup() {
17
-    super.setup();
16
+  protected init() {
17
+    super.init();
18
     SvgHelper.setAttributes(this.visual, [['class', 'highlight-marker']]);
18
     SvgHelper.setAttributes(this.visual, [['class', 'highlight-marker']]);
19
   }
19
   }
20
 }
20
 }

+ 3
- 3
src/markers/LineMarker/index.ts View File

9
   public static createMarker = (page?: WhitePage): LinearMarker => {
9
   public static createMarker = (page?: WhitePage): LinearMarker => {
10
     const marker = new LineMarker();
10
     const marker = new LineMarker();
11
     marker.page = page;
11
     marker.page = page;
12
-    marker.setup();
12
+    marker.init();
13
     return marker;
13
     return marker;
14
   };
14
   };
15
 
15
 
16
-  protected setup() {
17
-    super.setup();
16
+  protected init() {
17
+    super.init();
18
     SvgHelper.setAttributes(this.visual, [['class', 'line-marker']]);
18
     SvgHelper.setAttributes(this.visual, [['class', 'line-marker']]);
19
   }
19
   }
20
 }
20
 }

+ 7
- 6
src/markers/LinearMarker/index.ts View File

13
   public static createMarker = (page?: WhitePage): LinearMarker => {
13
   public static createMarker = (page?: WhitePage): LinearMarker => {
14
     const marker = new LinearMarker();
14
     const marker = new LinearMarker();
15
     marker.page = page;
15
     marker.page = page;
16
-    marker.setup();
16
+    marker.init();
17
     return marker;
17
     return marker;
18
   };
18
   };
19
 
19
 
78
     this.controlBox.style.display = 'none';
78
     this.controlBox.style.display = 'none';
79
   }
79
   }
80
 
80
 
81
-  protected setup() {
82
-    super.setup();
81
+  protected init() {
82
+    super.init();
83
 
83
 
84
     this.markerBgLine = SvgHelper.createLine(0, 0, this.x2, 0, [
84
     this.markerBgLine = SvgHelper.createLine(0, 0, this.x2, 0, [
85
       ['stroke', 'transparent'],
85
       ['stroke', 'transparent'],
138
     } else {
138
     } else {
139
       this.activeGrip = this.controlGrips.right;
139
       this.activeGrip = this.controlGrips.right;
140
     }
140
     }
141
-
142
     this.resize(x, y);
141
     this.resize(x, y);
143
   }
142
   }
144
 
143
 
145
   /** Init */
144
   /** Init */
146
-
147
   private addControlBox = () => {
145
   private addControlBox = () => {
148
     this.controlBox = SvgHelper.createGroup([['class', 'fc-whiteboard-line-control-box']]);
146
     this.controlBox = SvgHelper.createGroup([['class', 'fc-whiteboard-line-control-box']]);
149
     this.addToVisual(this.controlBox);
147
     this.addToVisual(this.controlBox);
150
-
151
     this.addControlGrips();
148
     this.addControlGrips();
152
   };
149
   };
153
 
150
 
177
     grip.visual.addEventListener('touchend', this.onTouch, { passive: false });
174
     grip.visual.addEventListener('touchend', this.onTouch, { passive: false });
178
     grip.visual.addEventListener('touchmove', this.onTouch, { passive: false });
175
     grip.visual.addEventListener('touchmove', this.onTouch, { passive: false });
179
 
176
 
177
+    if (this.page && this.page.mode === 'mirror') {
178
+      grip.visual.style.visibility = 'hidden';
179
+    }
180
+
180
     return grip;
181
     return grip;
181
   };
182
   };
182
 
183
 

+ 3
- 3
src/markers/RectMarker/RectBaseMarker.ts View File

8
   public static createMarker = (page?: WhitePage): RectBaseMarker => {
8
   public static createMarker = (page?: WhitePage): RectBaseMarker => {
9
     const marker = new RectBaseMarker();
9
     const marker = new RectBaseMarker();
10
     marker.page = page;
10
     marker.page = page;
11
-    marker.setup();
11
+    marker.init();
12
     return marker;
12
     return marker;
13
   };
13
   };
14
 
14
 
25
     }
25
     }
26
   }
26
   }
27
 
27
 
28
-  protected setup() {
29
-    super.setup();
28
+  protected init() {
29
+    super.init();
30
 
30
 
31
     this.markerRect = SvgHelper.createRect(this.width, this.height);
31
     this.markerRect = SvgHelper.createRect(this.width, this.height);
32
     this.addToRenderVisual(this.markerRect);
32
     this.addToRenderVisual(this.markerRect);

+ 3
- 3
src/markers/RectMarker/index.ts View File

9
   public static createMarker = (page?: WhitePage): RectBaseMarker => {
9
   public static createMarker = (page?: WhitePage): RectBaseMarker => {
10
     const marker = new RectMarker();
10
     const marker = new RectMarker();
11
     marker.page = page;
11
     marker.page = page;
12
-    marker.setup();
12
+    marker.init();
13
     return marker;
13
     return marker;
14
   };
14
   };
15
 
15
 
16
-  protected setup() {
17
-    super.setup();
16
+  protected init() {
17
+    super.init();
18
     SvgHelper.setAttributes(this.visual, [['class', 'rect-marker']]);
18
     SvgHelper.setAttributes(this.visual, [['class', 'rect-marker']]);
19
   }
19
   }
20
 }
20
 }

+ 3
- 3
src/markers/RectangularMarker/index.ts View File

10
   public static createMarker = (page?: WhitePage): RectangularMarker => {
10
   public static createMarker = (page?: WhitePage): RectangularMarker => {
11
     const marker = new RectangularMarker();
11
     const marker = new RectangularMarker();
12
     marker.page = page;
12
     marker.page = page;
13
-    marker.setup();
13
+    marker.init();
14
     return marker;
14
     return marker;
15
   };
15
   };
16
 
16
 
65
     this.controlBox.style.display = 'none';
65
     this.controlBox.style.display = 'none';
66
   }
66
   }
67
 
67
 
68
-  protected setup() {
69
-    super.setup();
68
+  protected init() {
69
+    super.init();
70
 
70
 
71
     this.addControlBox();
71
     this.addControlBox();
72
 
72
 

+ 3
- 3
src/markers/TextMarker/index.ts View File

14
   public static createMarker = (page?: WhitePage): TextMarker => {
14
   public static createMarker = (page?: WhitePage): TextMarker => {
15
     const marker = new TextMarker();
15
     const marker = new TextMarker();
16
     marker.page = page;
16
     marker.page = page;
17
-    marker.setup();
17
+    marker.init();
18
     return marker;
18
     return marker;
19
   };
19
   };
20
 
20
 
51
     }
51
     }
52
   }
52
   }
53
 
53
 
54
-  protected setup() {
55
-    super.setup();
54
+  protected init() {
55
+    super.init();
56
     this.textElement = SvgHelper.createText();
56
     this.textElement = SvgHelper.createText();
57
     this.addToRenderVisual(this.textElement);
57
     this.addToRenderVisual(this.textElement);
58
     SvgHelper.setAttributes(this.visual, [['class', 'text-marker']]);
58
     SvgHelper.setAttributes(this.visual, [['class', 'text-marker']]);

+ 62
- 0
src/renderer/DomEventAware/index.ts View File

1
+export abstract class DomEventAware {
2
+  x: number = 0;
3
+  y: number = 0;
4
+  previousMouseX: number = 0;
5
+  previousMouseY: number = 0;
6
+
7
+  protected init(ele: Element) {
8
+    ele.addEventListener('mousedown', this.onMouseDown);
9
+    ele.addEventListener('mouseup', this.onMouseUp);
10
+    ele.addEventListener('mousemove', this.onMouseMove);
11
+
12
+    ele.addEventListener('touchstart', this.onTouch, { passive: false });
13
+    ele.addEventListener('touchend', this.onTouch, { passive: false });
14
+    ele.addEventListener('touchmove', this.onTouch, { passive: false });
15
+  }
16
+
17
+  /** 截获 Touch 事件,并且转发为 Mouse 事件 */
18
+  protected onTouch(ev: TouchEvent) {
19
+    ev.preventDefault();
20
+    const newEvt = document.createEvent('MouseEvents');
21
+    const touch = ev.changedTouches[0];
22
+    let type = null;
23
+
24
+    switch (ev.type) {
25
+      case 'touchstart':
26
+        type = 'mousedown';
27
+        break;
28
+      case 'touchmove':
29
+        type = 'mousemove';
30
+        break;
31
+      case 'touchend':
32
+        type = 'mouseup';
33
+        break;
34
+      default:
35
+        break;
36
+    }
37
+
38
+    newEvt.initMouseEvent(
39
+      type!,
40
+      true,
41
+      true,
42
+      window,
43
+      0,
44
+      touch.screenX,
45
+      touch.screenY,
46
+      touch.clientX,
47
+      touch.clientY,
48
+      ev.ctrlKey,
49
+      ev.altKey,
50
+      ev.shiftKey,
51
+      ev.metaKey,
52
+      0,
53
+      null
54
+    );
55
+
56
+    ev.target!.dispatchEvent(newEvt);
57
+  }
58
+
59
+  protected abstract onMouseDown(ev: MouseEvent): void;
60
+  protected abstract onMouseUp(ev: MouseEvent): void;
61
+  protected abstract onMouseMove(ev: MouseEvent): void;
62
+}

+ 0
- 0
src/toolbar/EmbeddingToolbar/index.ts View File


+ 0
- 0
src/toolbar/FloatingToolbar/index.ts View File


+ 45
- 3
src/toolbar/Toolbar.ts View File

1
+import interact from 'interactjs';
2
+
1
 import { ToolbarButton } from './ToolbarButton';
3
 import { ToolbarButton } from './ToolbarButton';
2
 import { ToolbarItem } from './ToolbarItem';
4
 import { ToolbarItem } from './ToolbarItem';
3
 import { uuid } from '../utils/uuid';
5
 import { uuid } from '../utils/uuid';
4
 
6
 
5
-export class Toolbar {
7
+import './index.less';
8
+import { DomEventAware } from '../renderer/DomEventAware/index';
9
+import { dragToolbarItem } from './toolbar-items';
10
+import { Drawboard } from '../drawboard/Drawboard/index';
11
+
12
+export type MouseHandler = (ev: MouseEvent) => void;
13
+
14
+export class Toolbar extends DomEventAware {
6
   id: string = uuid();
15
   id: string = uuid();
7
   zIndex: number = 999;
16
   zIndex: number = 999;
8
 
17
 
9
   toolbarItems: ToolbarItem[];
18
   toolbarItems: ToolbarItem[];
10
   toolbarUI: HTMLElement;
19
   toolbarUI: HTMLElement;
20
+  toolbarButtons: ToolbarButton[] = [];
11
 
21
 
12
   clickHandler: (ev: MouseEvent, toolbarItem: ToolbarItem) => void;
22
   clickHandler: (ev: MouseEvent, toolbarItem: ToolbarItem) => void;
13
 
23
 
15
     toolbarItems: ToolbarItem[],
25
     toolbarItems: ToolbarItem[],
16
     clickHandler: (ev: MouseEvent, toolbarItem: ToolbarItem) => void
26
     clickHandler: (ev: MouseEvent, toolbarItem: ToolbarItem) => void
17
   ) {
27
   ) {
18
-    this.toolbarItems = toolbarItems;
28
+    super();
29
+
30
+    this.toolbarItems = [dragToolbarItem, ...toolbarItems];
19
     this.clickHandler = clickHandler;
31
     this.clickHandler = clickHandler;
20
   }
32
   }
21
 
33
 
22
   /** 获取 UI 元素 */
34
   /** 获取 UI 元素 */
23
-  public getUI = (): HTMLElement => {
35
+  public getUI = (drawboard: Drawboard): HTMLElement => {
24
     this.toolbarUI = document.createElement('div');
36
     this.toolbarUI = document.createElement('div');
25
     this.toolbarUI.id = `fcw-toolbar-${this.id}`;
37
     this.toolbarUI.id = `fcw-toolbar-${this.id}`;
26
     this.toolbarUI.className = 'fc-whiteboard-toolbar';
38
     this.toolbarUI.className = 'fc-whiteboard-toolbar';
27
 
39
 
28
     for (const toolbarItem of this.toolbarItems) {
40
     for (const toolbarItem of this.toolbarItems) {
29
       const toolbarButton = new ToolbarButton(toolbarItem, this.clickHandler);
41
       const toolbarButton = new ToolbarButton(toolbarItem, this.clickHandler);
42
+      toolbarButton.drawboard = drawboard;
30
       this.toolbarUI.appendChild(toolbarButton.getElement());
43
       this.toolbarUI.appendChild(toolbarButton.getElement());
44
+
45
+      this.toolbarButtons.push(toolbarButton);
31
     }
46
     }
32
 
47
 
48
+    super.init(this.toolbarUI);
49
+
50
+    interact('#drag-handler').draggable({
51
+      onmove: this.onDragMove
52
+    });
53
+
33
     return this.toolbarUI;
54
     return this.toolbarUI;
34
   };
55
   };
35
 
56
 
42
     this.toolbarUI.style.visibility = 'visible';
63
     this.toolbarUI.style.visibility = 'visible';
43
     this.toolbarUI.style.zIndex = `${this.zIndex}`;
64
     this.toolbarUI.style.zIndex = `${this.zIndex}`;
44
   }
65
   }
66
+
67
+  protected onMouseDown = (downEv: MouseEvent) => {};
68
+
69
+  protected onMouseUp = (ev: MouseEvent) => {};
70
+
71
+  protected onMouseMove = (ev: MouseEvent) => {};
72
+
73
+  protected onDragMove = (event: any) => {
74
+    var target = this.toolbarUI;
75
+
76
+    // keep the dragged position in the data-x/data-y attributes
77
+    var x = ((parseFloat(target.getAttribute('data-x') as string) || 0) + event.dx) as any;
78
+    var y = ((parseFloat(target.getAttribute('data-y') as string) || 0) + event.dy) as any;
79
+
80
+    // translate the element
81
+    target.style.webkitTransform = target.style.transform = 'translate(' + x + 'px, ' + y + 'px)';
82
+
83
+    // update the posiion attributes
84
+    target.setAttribute('data-x', x);
85
+    target.setAttribute('data-y', y);
86
+  };
45
 }
87
 }

+ 15
- 4
src/toolbar/ToolbarButton.ts View File

1
+import { uuid } from './../utils/uuid';
1
 import { ToolbarItem } from './ToolbarItem';
2
 import { ToolbarItem } from './ToolbarItem';
3
+import { Drawboard } from '../drawboard/Drawboard';
2
 
4
 
5
+/** 工作栏按钮 */
3
 export class ToolbarButton {
6
 export class ToolbarButton {
4
-  private toolbarItem: ToolbarItem;
5
-  private clickHandler: (ev: MouseEvent, toolbarItem: ToolbarItem) => void;
7
+  id = uuid();
8
+  drawboard: Drawboard;
9
+  toolbarItem: ToolbarItem;
10
+  container: HTMLDivElement;
11
+
12
+  clickHandler: (ev: MouseEvent, toolbarItem: ToolbarItem) => void;
6
 
13
 
7
   constructor(
14
   constructor(
8
     toolbarItem: ToolbarItem,
15
     toolbarItem: ToolbarItem,
17
 
24
 
18
   public getElement = (): HTMLElement => {
25
   public getElement = (): HTMLElement => {
19
     const div = document.createElement('div');
26
     const div = document.createElement('div');
27
+
20
     if (this.toolbarItem.name !== 'separator') {
28
     if (this.toolbarItem.name !== 'separator') {
21
       div.className = 'fc-whiteboard-toolbar-button';
29
       div.className = 'fc-whiteboard-toolbar-button';
30
+
22
       if (this.clickHandler) {
31
       if (this.clickHandler) {
23
         div.addEventListener('click', (ev: MouseEvent) => {
32
         div.addEventListener('click', (ev: MouseEvent) => {
24
           if (this.clickHandler) {
33
           if (this.clickHandler) {
28
       }
37
       }
29
 
38
 
30
       if (this.toolbarItem.icon) {
39
       if (this.toolbarItem.icon) {
31
-        div.title = this.toolbarItem.tooltipText;
40
+        div.title = this.toolbarItem.tooltipText || '';
32
         div.innerHTML = this.toolbarItem.icon;
41
         div.innerHTML = this.toolbarItem.icon;
33
       } else {
42
       } else {
34
-        div.innerText = this.toolbarItem.tooltipText;
43
+        div.innerText = this.toolbarItem.tooltipText || '';
35
       }
44
       }
36
     } else {
45
     } else {
37
       div.className = 'fc-whiteboard-toolbar-separator';
46
       div.className = 'fc-whiteboard-toolbar-separator';
38
     }
47
     }
39
 
48
 
49
+    this.container = div;
50
+
40
     return div;
51
     return div;
41
   };
52
   };
42
 }
53
 }

+ 12
- 12
src/toolbar/ToolbarItem.ts View File

1
 import { BaseMarker } from './../markers/BaseMarker/index';
1
 import { BaseMarker } from './../markers/BaseMarker/index';
2
+
3
+/** 对于工具栏的定义 */
2
 export class ToolbarItem {
4
 export class ToolbarItem {
3
   name: string;
5
   name: string;
4
-  tooltipText: string;
6
+  tooltipText?: string;
5
   icon?: string;
7
   icon?: string;
6
   markerType?: typeof BaseMarker;
8
   markerType?: typeof BaseMarker;
9
+  onClick?: () => void;
10
+  draggable?: boolean;
11
+
12
+  constructor({ name, tooltipText, icon, markerType, onClick, draggable }: Partial<ToolbarItem>) {
13
+    if (!name) {
14
+      throw new Error('Invalid params');
15
+    }
7
 
16
 
8
-  constructor({
9
-    name,
10
-    tooltipText,
11
-    icon,
12
-    markerType
13
-  }: {
14
-    name: string;
15
-    tooltipText: string;
16
-    icon?: string;
17
-    markerType?: typeof BaseMarker;
18
-  }) {
19
     this.name = name;
17
     this.name = name;
20
     this.tooltipText = tooltipText;
18
     this.tooltipText = tooltipText;
21
     this.icon = icon;
19
     this.icon = icon;
22
     this.markerType = markerType;
20
     this.markerType = markerType;
21
+    this.onClick = onClick;
22
+    this.draggable = draggable;
23
   }
23
   }
24
 }
24
 }

+ 49
- 0
src/toolbar/index.less View File

1
+.fc-whiteboard-toolbar {
2
+  height: 36px;
3
+  background: rgba(255, 255, 255, 1);
4
+  box-shadow: 0px 2px 9px 0px rgba(194, 185, 187, 0.29);
5
+  border-radius: 13px;
6
+  opacity: 0.9;
7
+  border: 1px solid rgba(230, 236, 247, 1);
8
+
9
+  display: flex;
10
+  align-items: center;
11
+}
12
+
13
+.fc-whiteboard-toolbar-button,
14
+.fc-whiteboard-toolbar-logo a {
15
+  display: inline-block;
16
+  margin: 2px;
17
+  padding: 3px;
18
+  cursor: pointer;
19
+  width: 20px;
20
+  height: 20px;
21
+  border-radius: 2px;
22
+  border-bottom: transparent solid 1px;
23
+  border-right: transparent solid 1px;
24
+
25
+  fill: #333333;
26
+
27
+  display: grid;
28
+  align-items: center;
29
+  justify-items: center;
30
+}
31
+
32
+.fc-whiteboard-toolbar-separator {
33
+  width: 0px;
34
+  height: 20px;
35
+  margin: 5px 5px;
36
+  border: 1px solid #cad3df;
37
+}
38
+
39
+.fc-whiteboard-toolbar-button:hover,
40
+.fc-whiteboard-toolbar-logo a:hover {
41
+  background-color: #eeeeee;
42
+  background: radial-gradient(#eeeeee, #cccccc);
43
+
44
+  fill: #ff8080;
45
+}
46
+
47
+.fc-whiteboard-toolbar-button svg {
48
+  height: 16px;
49
+}

+ 17
- 16
src/toolbar/toolbar-items.ts View File

12
 const PointerIcon = require('../assets/mouse-pointer.svg');
12
 const PointerIcon = require('../assets/mouse-pointer.svg');
13
 const CloseIcon = require('../assets/times.svg');
13
 const CloseIcon = require('../assets/times.svg');
14
 
14
 
15
+export const dragToolbarItem = new ToolbarItem({
16
+  name: 'drag',
17
+  tooltipText: 'Drag',
18
+  icon: require('../assets/drag.svg')
19
+});
20
+
15
 export const highlightMarkerToolbarItem = new ToolbarItem({
21
 export const highlightMarkerToolbarItem = new ToolbarItem({
16
   name: 'cover-marker',
22
   name: 'cover-marker',
17
   tooltipText: 'Cover',
23
   tooltipText: 'Cover',
54
   markerType: LineMarker
60
   markerType: LineMarker
55
 });
61
 });
56
 
62
 
63
+export const closeToolbarItem = new ToolbarItem({
64
+  icon: CloseIcon,
65
+  name: 'close',
66
+  tooltipText: 'Close'
67
+});
68
+
69
+export const separatorToolbarItem = new ToolbarItem({ name: 'separator', tooltipText: '' });
70
+
57
 export function getToolbars(page?: WhitePage) {
71
 export function getToolbars(page?: WhitePage) {
58
   const toolbars = [
72
   const toolbars = [
59
     {
73
     {
60
       icon: PointerIcon,
74
       icon: PointerIcon,
61
       name: 'pointer',
75
       name: 'pointer',
62
-      tooltipText: 'Pointer'
76
+      tooltipText: 'Pointer',
77
+      draggable: true
63
     },
78
     },
64
     {
79
     {
65
       icon: DeleteIcon,
80
       icon: DeleteIcon,
66
       name: 'delete',
81
       name: 'delete',
67
       tooltipText: 'Delete'
82
       tooltipText: 'Delete'
68
     },
83
     },
69
-    {
70
-      name: 'separator',
71
-      tooltipText: ''
72
-    },
73
     rectMarkerToolbarItem,
84
     rectMarkerToolbarItem,
74
     coverMarkerToolbarItem,
85
     coverMarkerToolbarItem,
75
     highlightMarkerToolbarItem,
86
     highlightMarkerToolbarItem,
76
     lineMarkerToolbarItem,
87
     lineMarkerToolbarItem,
77
     arrowMarkerToolbarItem,
88
     arrowMarkerToolbarItem,
78
-    textMarkerToolbarItem,
79
-    {
80
-      name: 'separator',
81
-      tooltipText: ''
82
-    }
89
+    textMarkerToolbarItem
83
   ];
90
   ];
84
 
91
 
85
   if (!page) {
92
   if (!page) {
94
     );
101
     );
95
   }
102
   }
96
 
103
 
97
-  toolbars.push({
98
-    icon: CloseIcon,
99
-    name: 'close',
100
-    tooltipText: 'Close'
101
-  });
102
-
103
   return toolbars;
104
   return toolbars;
104
 }
105
 }

+ 4
- 0
src/utils/types.ts View File

8
   // 需要展示的图片地址
8
   // 需要展示的图片地址
9
   imgSrc?: string;
9
   imgSrc?: string;
10
 };
10
 };
11
+
12
+export function isNil(mayBeNil: any) {
13
+  return mayBeNil === null || mayBeNil === undefined;
14
+}

+ 2
- 0
src/whiteboard/AbstractWhiteboard/index.less View File

1
 .fcw-board {
1
 .fcw-board {
2
+  position: relative;
3
+
2
   // 设置需要元素的绝对布局
4
   // 设置需要元素的绝对布局
3
   &-imgs,
5
   &-imgs,
4
   &-pages,
6
   &-pages,

+ 2
- 0
src/whiteboard/AbstractWhiteboard/index.ts View File

19
   /** 元素 */
19
   /** 元素 */
20
   // 如果传入的是图片地址,则需要挂载到该 Target 元素下
20
   // 如果传入的是图片地址,则需要挂载到该 Target 元素下
21
   target: HTMLDivElement;
21
   target: HTMLDivElement;
22
+  imgEles: HTMLImageElement[] = [];
22
   imgsContainer: HTMLDivElement;
23
   imgsContainer: HTMLDivElement;
23
   pagesContainer: HTMLDivElement;
24
   pagesContainer: HTMLDivElement;
24
 
25
 
169
       imgEle.src = source;
170
       imgEle.src = source;
170
       imgEle.alt = 'Siema image';
171
       imgEle.alt = 'Siema image';
171
 
172
 
173
+      this.imgEles.push(imgEle);
172
       this.imgsContainer.appendChild(imgEle);
174
       this.imgsContainer.appendChild(imgEle);
173
     });
175
     });
174
 
176
 

+ 1
- 2
src/whiteboard/MirrorWhiteboard/index.ts View File

66
 
66
 
67
   /** 响应获取到的快照事件 */
67
   /** 响应获取到的快照事件 */
68
   private applySnap(snap: WhiteboardSnap) {
68
   private applySnap(snap: WhiteboardSnap) {
69
-    const { id, sources, pageIds, visiblePageIndex } = snap;
69
+    const { id, sources, pageIds } = snap;
70
 
70
 
71
     if (!this.isInitialized && !this.isSyncing) {
71
     if (!this.isInitialized && !this.isSyncing) {
72
       this.id = id;
72
       this.id = id;
96
       this.initSiema();
96
       this.initSiema();
97
       this.isInitialized = true;
97
       this.isInitialized = true;
98
       this.isSyncing = false;
98
       this.isSyncing = false;
99
-      this.onPageChange(visiblePageIndex);
100
     }
99
     }
101
 
100
 
102
     // 如果已经初始化完毕,则进行状态同步
101
     // 如果已经初始化完毕,则进行状态同步

+ 119
- 0
src/whiteboard/ReplayWhiteboard/index.ts View File

1
+import { MirrorWhiteboard } from '../MirrorWhiteboard';
2
+import { SyncEvent } from '../../event/SyncEvent';
3
+import { EventHub } from 'fc-whiteboard/src/event/EventHub';
4
+
5
+// 窗口大小为一分钟
6
+const windowSize = 60 * 1000;
7
+const duration = 0.05;
8
+
9
+/** 根据某个时间点,获取一系列的 Event,获取的是绝对时间;这里的 startTime 和 endTime 都是当初记录时候关联的绝对时间 */
10
+export type onLoadFunc = (startTime: number, endTime: number) => Promise<SyncEvent[]>;
11
+
12
+export class ReplayWhiteboard extends MirrorWhiteboard {
13
+  leftEvents: SyncEvent[] = [];
14
+
15
+  // 记录开始时间
16
+  recordStartTime: number;
17
+  // 当前的相对时间
18
+  currentRelativeTime: number = 0;
19
+  loadedRelativeTime: number = -1;
20
+
21
+  // Inner Handler
22
+  interval: any;
23
+  loadingLock: boolean = false;
24
+  seekingLock: boolean = false;
25
+  onLoad: onLoadFunc;
26
+
27
+  /** Getter & Setter */
28
+  /** 设置录播相关的上下文信息 */
29
+  setContext(recordStartTime: number, onLoad: onLoadFunc) {
30
+    this.recordStartTime = recordStartTime;
31
+    this.onLoad = onLoad;
32
+
33
+    if (this.interval) {
34
+      clearInterval(this.interval);
35
+    }
36
+
37
+    this.interval = setInterval(() => {
38
+      this.seek();
39
+    }, duration * 1000);
40
+  }
41
+
42
+  setCurrentRelativeTime(time: number) {
43
+    this.currentRelativeTime = time;
44
+    this.loadedRelativeTime = this.currentRelativeTime - 1;
45
+  }
46
+
47
+  /** 扩展父类的关闭函数 */
48
+  close() {
49
+    super.close();
50
+
51
+    if (this.interval) {
52
+      clearInterval(this.interval);
53
+    }
54
+  }
55
+
56
+  protected init() {
57
+    this.eventHub = new EventHub();
58
+
59
+    super.init();
60
+  }
61
+
62
+  /** 加载全部的事件 */
63
+  private loadEvents() {
64
+    if (this.onLoad && !this.loadingLock) {
65
+      this.loadingLock = true;
66
+
67
+      const startTime = this.recordStartTime + this.currentRelativeTime;
68
+      const endTime = startTime + windowSize;
69
+
70
+      this.onLoad(startTime, endTime)
71
+        .then(events => {
72
+          this.loadedRelativeTime = this.currentRelativeTime;
73
+          this.leftEvents.push(...(events || []));
74
+        })
75
+        .finally(() => {
76
+          this.loadingLock = false;
77
+        });
78
+    }
79
+  }
80
+
81
+  /** 回调,定期更新时间,执行操作 */
82
+  private seek = () => {
83
+    if (this.seekingLock) {
84
+      return;
85
+    }
86
+    this.seekingLock = true;
87
+    // 如果已经进入到了加载完毕的状态,则
88
+    if (this.loadedRelativeTime < this.currentRelativeTime) {
89
+      this.loadEvents();
90
+    }
91
+
92
+    // 获取需要回放的 Events,即所有小于当前时间点的 Events
93
+
94
+    const waitingEvents: SyncEvent[] = [];
95
+    const leftEvents: SyncEvent[] = [];
96
+
97
+    this.leftEvents.forEach((e, i) => {
98
+      if (e.timestamp && e.timestamp < this.currentRelativeTime + this.recordStartTime) {
99
+        waitingEvents.push(e);
100
+      } else {
101
+        leftEvents.push(e);
102
+      }
103
+    });
104
+
105
+    this.leftEvents = leftEvents;
106
+
107
+    waitingEvents.forEach(e => {
108
+      this.perform(e);
109
+    });
110
+
111
+    this.currentRelativeTime += duration;
112
+    this.seekingLock = false;
113
+  };
114
+
115
+  /** 执行事件回放操作 */
116
+  private perform = (e: SyncEvent) => {
117
+    this.eventHub!.emit('sync', e);
118
+  };
119
+}

+ 9
- 4
src/whiteboard/WhitePage/index.ts View File

9
 import { AbstractWhiteboard } from '../AbstractWhiteboard/index';
9
 import { AbstractWhiteboard } from '../AbstractWhiteboard/index';
10
 
10
 
11
 import './index.less';
11
 import './index.less';
12
+import { ToolbarItem } from 'fc-whiteboard/src/toolbar/ToolbarItem';
12
 
13
 
13
 const prefix = 'fcw-page';
14
 const prefix = 'fcw-page';
14
 
15
 
34
     {
35
     {
35
       mode,
36
       mode,
36
       whiteboard,
37
       whiteboard,
37
-      parentContainer
38
-    }: { mode?: Mode; whiteboard?: AbstractWhiteboard; parentContainer?: HTMLDivElement } = {}
38
+      parentContainer,
39
+      extraToolbarItems
40
+    }: Partial<WhitePage> & {
41
+      extraToolbarItems?: ToolbarItem[];
42
+    } = {}
39
   ) {
43
   ) {
40
     if (mode) {
44
     if (mode) {
41
       this.mode = mode;
45
       this.mode = mode;
46
     this.initSource(source);
50
     this.initSource(source);
47
 
51
 
48
     if (this.mode === 'master') {
52
     if (this.mode === 'master') {
49
-      this.initMaster();
53
+      this.initMaster(extraToolbarItems);
50
     }
54
     }
51
 
55
 
52
     if (this.mode === 'mirror') {
56
     if (this.mode === 'mirror') {
126
   }
130
   }
127
 
131
 
128
   /** 以 Master 模式启动 */
132
   /** 以 Master 模式启动 */
129
-  protected initMaster() {
133
+  protected initMaster(extraToolbarItems?: ToolbarItem[]) {
130
     if (this.whiteboard) {
134
     if (this.whiteboard) {
131
       // 对于 WhitePage 中加载的 Drawboard,必须是传入自身可控的 Image 元素
135
       // 对于 WhitePage 中加载的 Drawboard,必须是传入自身可控的 Image 元素
132
       this.drawboard = new Drawboard(
136
       this.drawboard = new Drawboard(
133
         { imgEle: this.target },
137
         { imgEle: this.target },
134
         {
138
         {
135
           page: this,
139
           page: this,
140
+          extraToolbarItems,
136
           onChange: ev => this.whiteboard!.emit(ev)
141
           onChange: ev => this.whiteboard!.emit(ev)
137
         }
142
         }
138
       );
143
       );

+ 27
- 22
src/whiteboard/Whiteboard/index.ts View File

2
 import { createDivWithClassName } from '../../utils/dom';
2
 import { createDivWithClassName } from '../../utils/dom';
3
 import { AbstractWhiteboard } from '../AbstractWhiteboard/index';
3
 import { AbstractWhiteboard } from '../AbstractWhiteboard/index';
4
 import { Mode } from '../../utils/types';
4
 import { Mode } from '../../utils/types';
5
+import { separatorToolbarItem } from '../../toolbar/toolbar-items';
5
 
6
 
6
 const LeftArrowIcon = require('../../assets/bx-left-arrow.svg');
7
 const LeftArrowIcon = require('../../assets/bx-left-arrow.svg');
7
 const RightArrowIcon = require('../../assets/bx-right-arrow.svg');
8
 const RightArrowIcon = require('../../assets/bx-right-arrow.svg');
25
 
26
 
26
   /** 以主模式启动 */
27
   /** 以主模式启动 */
27
   private initMaster() {
28
   private initMaster() {
29
+    // 初始化控制节点
30
+    const prevToolbarItem = {
31
+      icon: LeftArrowIcon,
32
+      name: 'prev-flip-arrow',
33
+      tooltipText: 'Prev',
34
+      onClick: () => {
35
+        const nextPageIndex =
36
+          this.visiblePageIndex + 1 > this.pages.length - 1 ? 0 : this.visiblePageIndex + 1;
37
+        this.onPageChange(nextPageIndex);
38
+      }
39
+    };
40
+
41
+    const nextToolbarItem = {
42
+      icon: RightArrowIcon,
43
+      name: 'next-flip-arrow',
44
+      tooltipText: 'Next',
45
+      onClick: () => {
46
+        const nextPageIndex =
47
+          this.visiblePageIndex - 1 < 0 ? this.pages.length - 1 : this.visiblePageIndex - 1;
48
+
49
+        this.onPageChange(nextPageIndex);
50
+      }
51
+    };
52
+
28
     // 初始化所有的 WhitePages
53
     // 初始化所有的 WhitePages
29
     this.sources.forEach(source => {
54
     this.sources.forEach(source => {
30
       const page = new WhitePage(
55
       const page = new WhitePage(
32
         {
57
         {
33
           mode: this.mode,
58
           mode: this.mode,
34
           whiteboard: this,
59
           whiteboard: this,
35
-          parentContainer: this.pagesContainer
60
+          parentContainer: this.pagesContainer,
61
+          extraToolbarItems: [separatorToolbarItem, prevToolbarItem, nextToolbarItem]
36
         }
62
         }
37
       );
63
       );
38
 
64
 
43
     });
69
     });
44
 
70
 
45
     this.initSiema();
71
     this.initSiema();
46
-
47
-    // 初始化控制节点
48
-    const controller = createDivWithClassName(`${prefix}-controller`, this.target);
49
-
50
-    const prevEle = createDivWithClassName(`${prefix}-flip-arrow`, controller);
51
-    prevEle.innerHTML = LeftArrowIcon;
52
-
53
-    const nextEle = createDivWithClassName(`${prefix}-flip-arrow`, controller);
54
-    nextEle.innerHTML = RightArrowIcon;
55
-
56
-    nextEle!.addEventListener('click', () => {
57
-      const nextPageIndex =
58
-        this.visiblePageIndex + 1 > this.pages.length - 1 ? 0 : this.visiblePageIndex + 1;
59
-      this.onPageChange(nextPageIndex);
60
-    });
61
-    prevEle!.addEventListener('click', () => {
62
-      const nextPageIndex =
63
-        this.visiblePageIndex - 1 < 0 ? this.pages.length - 1 : this.visiblePageIndex - 1;
64
-
65
-      this.onPageChange(nextPageIndex);
66
-    });
67
   }
72
   }
68
 
73
 
69
   /** 响应页面切换的事件 */
74
   /** 响应页面切换的事件 */