123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388 |
- import { WhitePageSource } from './../types';
- import { Baseboard } from './../Baseboard/index';
- import { BaseMarker } from './../../markers/BaseMarker/index';
- import { getToolbars } from './../../toolbar/toolbar-items';
- import { WhitePage } from './../WhitePage/index';
- import { onSyncFunc } from './../../event/Event';
-
- import { Synthetizer } from '../../renderer/Synthetizer';
- import { Toolbar } from '../../toolbar/Toolbar';
- import { ToolbarItem } from '../../toolbar/ToolbarItem';
-
- import './index.less';
-
- export class Drawboard extends Baseboard {
- /** Options */
- private scale = 1.0;
-
- /** 句柄 */
- page: WhitePage;
-
- private markers: BaseMarker[];
- get markerMap(): { [key: string]: BaseMarker } {
- const map = {};
- this.markers.forEach(marker => {
- map[marker.id] = marker;
- });
- return map;
- }
- private activeMarker: BaseMarker | null;
-
- private toolbar: Toolbar;
- private toolbars: ToolbarItem[];
- private toolbarUI: HTMLElement;
-
- /** 回调 */
- private onComplete: (dataUrl: string) => void = () => {};
- private onChange: onSyncFunc = () => {};
- private onCancel: () => void;
-
- constructor(
- source: WhitePageSource,
- { page, onChange }: { page?: WhitePage; onChange?: onSyncFunc } = {}
- ) {
- super(source);
-
- if (page) {
- this.page = page;
- }
-
- this.markers = [];
- this.activeMarker = null;
- this.toolbars = getToolbars(page);
-
- if (onChange) {
- this.onChange = onChange;
- }
- }
-
- /** @region LifeCycle open - hide - show - ... - close */
- /** 打开画板 */
- public open = (onComplete?: (dataUrl: string) => void, onCancel?: () => void) => {
- if (onComplete) {
- this.onComplete = onComplete;
- }
-
- if (onCancel) {
- this.onCancel = onCancel;
- }
-
- this.setTargetRect();
-
- this.initBoard();
- this.attachEvents();
- this.setStyles();
-
- window.addEventListener('resize', this.adjustUI);
-
- if (this.page.mode === 'master') {
- this.showUI();
- }
- };
-
- public hide = () => {
- if (this.source.imgSrc) {
- this.target.style.display = 'none';
- }
- // 这里不使用 display:none,是为了保证在隐藏时候仍然可以执行更新
- this.boardHolder.style.opacity = '0';
- this.boardHolder.style.zIndex = '-1';
-
- if (this.toolbar) {
- this.toolbar.hide();
- }
- };
-
- public show = () => {
- if (this.source.imgSrc) {
- this.target.style.display = 'block';
- }
-
- this.boardHolder.style.opacity = '1';
- this.boardHolder.style.zIndex = '9999';
-
- if (this.toolbar) {
- this.toolbar.show();
- }
- };
-
- public close = () => {
- if (this.toolbarUI) {
- document.body.removeChild(this.toolbarUI);
- }
- if (this.boardCanvas) {
- document.body.removeChild(this.boardHolder);
- }
- };
-
- public render = (onComplete: (dataUrl: string) => void, onCancel?: () => void) => {
- this.onComplete = onComplete;
-
- if (onCancel) {
- this.onCancel = onCancel;
- }
-
- this.selectMarker(null);
- this.startRender(this.renderFinished);
- };
-
- public addMarker = (markerType: typeof BaseMarker, { id }: { id?: string } = {}) => {
- // 假如 Drawboard 存在 Page 引用,则传导给 Marker
- const marker = markerType.createMarker(this.page);
-
- if (id) {
- marker.id = id;
- }
-
- marker.onSelected = this.selectMarker;
- marker.onChange = this.onChange;
-
- if (marker.defs && marker.defs.length > 0) {
- for (const d of marker.defs) {
- if (d.id && !this.boardCanvas.getElementById(d.id)) {
- this.defs.appendChild(d);
- }
- }
- }
-
- // 触发事件流
- this.onChange({
- target: 'marker',
- parentId: this.page ? this.page.id : this.id,
- event: 'add',
- data: { type: marker.type, id: marker.id }
- });
-
- this.markers.push(marker);
-
- this.selectMarker(marker);
-
- this.boardCanvas.appendChild(marker.visual);
-
- const bbox = marker.visual.getBBox();
- const x = this.width / 2 / this.scale - bbox.width / 2;
- const y = this.height / 2 / this.scale - bbox.height / 2;
-
- const translate = marker.visual.transform.baseVal.getItem(0);
- translate.setMatrix(translate.matrix.translate(x, y));
- marker.visual.transform.baseVal.replaceItem(translate, 0);
- };
-
- public deleteActiveMarker = () => {
- if (this.activeMarker) {
- // 触发事件
- if (this.onChange) {
- this.onChange({
- event: 'remove',
- id: this.activeMarker.id,
- target: 'marker',
- data: { id: this.activeMarker.id }
- });
- }
- this.deleteMarker(this.activeMarker);
- }
- };
-
- private setTargetRect = () => {
- const targetRect = this.target.getBoundingClientRect() as DOMRect;
- const bodyRect = document.body.parentElement!.getBoundingClientRect();
- this.targetRect = {
- left: targetRect.left - bodyRect.left,
- top: targetRect.top - bodyRect.top
- } as ClientRect;
- };
-
- private startRender = (done: (dataUrl: string) => void) => {
- const renderer = new Synthetizer();
- renderer.rasterize(this.target, this.boardCanvas, done);
- };
-
- private attachEvents = () => {
- this.boardCanvas.addEventListener('mousedown', this.mouseDown);
- this.boardCanvas.addEventListener('mousemove', this.mouseMove);
- this.boardCanvas.addEventListener('mouseup', this.mouseUp);
- };
-
- private mouseDown = (ev: MouseEvent) => {
- /* tslint:disable:no-bitwise */
- if (this.activeMarker && (ev.buttons & 1) > 0) {
- this.activeMarker.deselect();
- this.activeMarker = null;
- }
- };
-
- private mouseMove = (ev: MouseEvent) => {
- /* tslint:disable:no-bitwise */
- if (this.activeMarker && (ev.buttons & 1) > 0) {
- this.activeMarker.manipulate(ev);
- }
- };
-
- private mouseUp = (ev: MouseEvent) => {
- if (this.activeMarker) {
- this.activeMarker.endManipulation();
- }
- };
-
- private adjustUI = (ev: UIEvent) => {
- this.adjustSize();
- this.positionUI();
- };
-
- private adjustSize = () => {
- this.width = this.target.clientWidth;
- this.height = this.target.clientHeight;
-
- const scale = this.target.clientWidth / this.boardHolder.clientWidth;
- if (scale !== 1.0) {
- this.scale *= scale;
- this.boardHolder.style.width = `${this.width}px`;
- this.boardHolder.style.height = `${this.height}px`;
-
- this.boardHolder.style.transform = `scale(${this.scale})`;
- }
- };
-
- private positionUI = () => {
- this.setTargetRect();
- this.positionBoard();
- this.positionToolbar();
- };
-
- private positionToolbar = () => {
- this.toolbarUI.style.left = `${this.targetRect.left +
- this.target.offsetWidth -
- this.toolbarUI.clientWidth}px`;
- this.toolbarUI.style.top = `${this.targetRect.top - this.toolbarUI.clientHeight}px`;
- };
-
- private showUI = () => {
- this.toolbar = new Toolbar(this.toolbars, this.toolbarClick);
- this.toolbarUI = this.toolbar.getUI();
- document.body.appendChild(this.toolbarUI);
- this.toolbarUI.style.position = 'absolute';
- this.positionToolbar();
- };
-
- private setStyles = () => {
- const editorStyleSheet = document.createElementNS('http://www.w3.org/2000/svg', 'style');
- editorStyleSheet.innerHTML = `
- .rect-marker .render-visual {
- stroke: #ff0000;
- stroke-width: 3;
- fill: transparent;
- }
- .cover-marker .render-visual {
- stroke-width: 0;
- fill: #000000;
- }
- .highlight-marker .render-visual {
- stroke: transparent;
- stroke-width: 0;
- fill: #ffff00;
- fill-opacity: 0.4;
- }
- .line-marker .render-visual {
- stroke: #ff0000;
- stroke-width: 3;
- fill: transparent;
- }
- .arrow-marker .render-visual {
- stroke: #ff0000;
- stroke-width: 3;
- fill: transparent;
- }
- .arrow-marker-tip {
- stroke-width: 0;
- fill: #ff0000;
- }
- .text-marker text {
- fill: #ff0000;
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI",
- Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji",
- "Segoe UI Emoji", "Segoe UI Symbol";
- }
- .fc-whiteboard-rect-control-box .fc-whiteboard-rect-control-rect {
- stroke: black;
- stroke-width: 1;
- stroke-opacity: 0.5;
- stroke-dasharray: 3, 2;
- fill: transparent;
- }
- .fc-whiteboard-control-grip {
- fill: #cccccc;
- stroke: #333333;
- stroke-width: 2;
- }
- `;
-
- this.boardCanvas.appendChild(editorStyleSheet);
- };
-
- private toolbarClick = (ev: MouseEvent, toolbarItem: ToolbarItem) => {
- if (toolbarItem.markerType) {
- this.addMarker(toolbarItem.markerType);
- } else {
- // command button
- switch (toolbarItem.name) {
- case 'delete': {
- this.deleteActiveMarker();
- break;
- }
- case 'pointer': {
- if (this.activeMarker) {
- this.selectMarker(null);
- }
- break;
- }
- case 'close': {
- this.cancel();
- break;
- }
- case 'ok': {
- this.complete();
- break;
- }
- default:
- break;
- }
- }
- };
-
- private selectMarker = (marker: BaseMarker | null) => {
- if (this.activeMarker && this.activeMarker !== marker) {
- this.activeMarker.deselect();
- }
- this.activeMarker = marker;
- };
-
- public deleteMarker = (marker: BaseMarker) => {
- this.boardCanvas.removeChild(marker.visual);
- if (this.activeMarker === marker) {
- this.activeMarker = null;
- }
- this.markers.splice(this.markers.indexOf(marker), 1);
- };
-
- private complete = () => {
- this.selectMarker(null);
- this.startRender(this.renderFinishedClose);
- };
-
- private cancel = () => {
- this.close();
- if (this.onCancel) {
- this.onCancel();
- }
- };
-
- private renderFinished = (dataUrl: string) => {
- this.onComplete(dataUrl);
- };
-
- private renderFinishedClose = (dataUrl: string) => {
- this.close();
- this.onComplete(dataUrl);
- };
- }
|