UNPKG

@drizm/ng-whiteboard

Version:

A Canvas component for Angular which supports free drawing.

2,083 lines 110 kB
(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core'), require('rxjs'), require('rxjs/operators'), require('lodash-es'), require('@angular/common')) :
    typeof define === 'function' && define.amd ? define('@drizm/ng-whiteboard', ['exports', '@angular/core', 'rxjs', 'rxjs/operators', 'lodash-es', '@angular/common'], factory) :
    (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.drizm = global.drizm || {}, global.drizm['ng-whiteboard'] = {}), global.ng.core, global.rxjs, global.rxjs.operators, global.lodashEs, global.ng.common));
}(this, (function (exports, core, rxjs, operators, lodashEs, common) { 'use strict';

    /*! *****************************************************************************
    Copyright (c) Microsoft Corporation.

    Permission to use, copy, modify, and/or distribute this software for any
    purpose with or without fee is hereby granted.

    THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
    REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
    AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
    INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
    LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
    OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
    PERFORMANCE OF THIS SOFTWARE.
    ***************************************************************************** */
    /* global Reflect, Promise */
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b)
                if (b.hasOwnProperty(p))
                    d[p] = b[p]; };
        return extendStatics(d, b);
    };
    function __extends(d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    }
    var __assign = function () {
        __assign = Object.assign || function __assign(t) {
            for (var s, i = 1, n = arguments.length; i < n; i++) {
                s = arguments[i];
                for (var p in s)
                    if (Object.prototype.hasOwnProperty.call(s, p))
                        t[p] = s[p];
            }
            return t;
        };
        return __assign.apply(this, arguments);
    };
    function __rest(s, e) {
        var t = {};
        for (var p in s)
            if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
                t[p] = s[p];
        if (s != null && typeof Object.getOwnPropertySymbols === "function")
            for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
                if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
                    t[p[i]] = s[p[i]];
            }
        return t;
    }
    function __decorate(decorators, target, key, desc) {
        var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
        if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
            r = Reflect.decorate(decorators, target, key, desc);
        else
            for (var i = decorators.length - 1; i >= 0; i--)
                if (d = decorators[i])
                    r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
        return c > 3 && r && Object.defineProperty(target, key, r), r;
    }
    function __param(paramIndex, decorator) {
        return function (target, key) { decorator(target, key, paramIndex); };
    }
    function __metadata(metadataKey, metadataValue) {
        if (typeof Reflect === "object" && typeof Reflect.metadata === "function")
            return Reflect.metadata(metadataKey, metadataValue);
    }
    function __awaiter(thisArg, _arguments, P, generator) {
        function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
        return new (P || (P = Promise))(function (resolve, reject) {
            function fulfilled(value) { try {
                step(generator.next(value));
            }
            catch (e) {
                reject(e);
            } }
            function rejected(value) { try {
                step(generator["throw"](value));
            }
            catch (e) {
                reject(e);
            } }
            function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
            step((generator = generator.apply(thisArg, _arguments || [])).next());
        });
    }
    function __generator(thisArg, body) {
        var _ = { label: 0, sent: function () { if (t[0] & 1)
                throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
        return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function () { return this; }), g;
        function verb(n) { return function (v) { return step([n, v]); }; }
        function step(op) {
            if (f)
                throw new TypeError("Generator is already executing.");
            while (_)
                try {
                    if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done)
                        return t;
                    if (y = 0, t)
                        op = [op[0] & 2, t.value];
                    switch (op[0]) {
                        case 0:
                        case 1:
                            t = op;
                            break;
                        case 4:
                            _.label++;
                            return { value: op[1], done: false };
                        case 5:
                            _.label++;
                            y = op[1];
                            op = [0];
                            continue;
                        case 7:
                            op = _.ops.pop();
                            _.trys.pop();
                            continue;
                        default:
                            if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) {
                                _ = 0;
                                continue;
                            }
                            if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) {
                                _.label = op[1];
                                break;
                            }
                            if (op[0] === 6 && _.label < t[1]) {
                                _.label = t[1];
                                t = op;
                                break;
                            }
                            if (t && _.label < t[2]) {
                                _.label = t[2];
                                _.ops.push(op);
                                break;
                            }
                            if (t[2])
                                _.ops.pop();
                            _.trys.pop();
                            continue;
                    }
                    op = body.call(thisArg, _);
                }
                catch (e) {
                    op = [6, e];
                    y = 0;
                }
                finally {
                    f = t = 0;
                }
            if (op[0] & 5)
                throw op[1];
            return { value: op[0] ? op[1] : void 0, done: true };
        }
    }
    var __createBinding = Object.create ? (function (o, m, k, k2) {
        if (k2 === undefined)
            k2 = k;
        Object.defineProperty(o, k2, { enumerable: true, get: function () { return m[k]; } });
    }) : (function (o, m, k, k2) {
        if (k2 === undefined)
            k2 = k;
        o[k2] = m[k];
    });
    function __exportStar(m, exports) {
        for (var p in m)
            if (p !== "default" && !exports.hasOwnProperty(p))
                __createBinding(exports, m, p);
    }
    function __values(o) {
        var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
        if (m)
            return m.call(o);
        if (o && typeof o.length === "number")
            return {
                next: function () {
                    if (o && i >= o.length)
                        o = void 0;
                    return { value: o && o[i++], done: !o };
                }
            };
        throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
    }
    function __read(o, n) {
        var m = typeof Symbol === "function" && o[Symbol.iterator];
        if (!m)
            return o;
        var i = m.call(o), r, ar = [], e;
        try {
            while ((n === void 0 || n-- > 0) && !(r = i.next()).done)
                ar.push(r.value);
        }
        catch (error) {
            e = { error: error };
        }
        finally {
            try {
                if (r && !r.done && (m = i["return"]))
                    m.call(i);
            }
            finally {
                if (e)
                    throw e.error;
            }
        }
        return ar;
    }
    function __spread() {
        for (var ar = [], i = 0; i < arguments.length; i++)
            ar = ar.concat(__read(arguments[i]));
        return ar;
    }
    function __spreadArrays() {
        for (var s = 0, i = 0, il = arguments.length; i < il; i++)
            s += arguments[i].length;
        for (var r = Array(s), k = 0, i = 0; i < il; i++)
            for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
                r[k] = a[j];
        return r;
    }
    ;
    function __await(v) {
        return this instanceof __await ? (this.v = v, this) : new __await(v);
    }
    function __asyncGenerator(thisArg, _arguments, generator) {
        if (!Symbol.asyncIterator)
            throw new TypeError("Symbol.asyncIterator is not defined.");
        var g = generator.apply(thisArg, _arguments || []), i, q = [];
        return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i;
        function verb(n) { if (g[n])
            i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }
        function resume(n, v) { try {
            step(g[n](v));
        }
        catch (e) {
            settle(q[0][3], e);
        } }
        function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
        function fulfill(value) { resume("next", value); }
        function reject(value) { resume("throw", value); }
        function settle(f, v) { if (f(v), q.shift(), q.length)
            resume(q[0][0], q[0][1]); }
    }
    function __asyncDelegator(o) {
        var i, p;
        return i = {}, verb("next"), verb("throw", function (e) { throw e; }), verb("return"), i[Symbol.iterator] = function () { return this; }, i;
        function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === "return" } : f ? f(v) : v; } : f; }
    }
    function __asyncValues(o) {
        if (!Symbol.asyncIterator)
            throw new TypeError("Symbol.asyncIterator is not defined.");
        var m = o[Symbol.asyncIterator], i;
        return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
        function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
        function settle(resolve, reject, d, v) { Promise.resolve(v).then(function (v) { resolve({ value: v, done: d }); }, reject); }
    }
    function __makeTemplateObject(cooked, raw) {
        if (Object.defineProperty) {
            Object.defineProperty(cooked, "raw", { value: raw });
        }
        else {
            cooked.raw = raw;
        }
        return cooked;
    }
    ;
    var __setModuleDefault = Object.create ? (function (o, v) {
        Object.defineProperty(o, "default", { enumerable: true, value: v });
    }) : function (o, v) {
        o["default"] = v;
    };
    function __importStar(mod) {
        if (mod && mod.__esModule)
            return mod;
        var result = {};
        if (mod != null)
            for (var k in mod)
                if (Object.hasOwnProperty.call(mod, k))
                    __createBinding(result, mod, k);
        __setModuleDefault(result, mod);
        return result;
    }
    function __importDefault(mod) {
        return (mod && mod.__esModule) ? mod : { default: mod };
    }
    function __classPrivateFieldGet(receiver, privateMap) {
        if (!privateMap.has(receiver)) {
            throw new TypeError("attempted to get private field on non-instance");
        }
        return privateMap.get(receiver);
    }
    function __classPrivateFieldSet(receiver, privateMap, value) {
        if (!privateMap.has(receiver)) {
            throw new TypeError("attempted to set private field on non-instance");
        }
        privateMap.set(receiver, value);
        return value;
    }

    var CanvasWhiteboardUpdateType;
    (function (CanvasWhiteboardUpdateType) {
        CanvasWhiteboardUpdateType[CanvasWhiteboardUpdateType["START"] = 0] = "START";
        CanvasWhiteboardUpdateType[CanvasWhiteboardUpdateType["DRAG"] = 1] = "DRAG";
        CanvasWhiteboardUpdateType[CanvasWhiteboardUpdateType["STOP"] = 2] = "STOP";
    })(CanvasWhiteboardUpdateType || (CanvasWhiteboardUpdateType = {}));
    var CanvasWhiteboardUpdate = /** @class */ (function () {
        function CanvasWhiteboardUpdate(x, y, type, UUID, selectedShape, selectedShapeOptions) {
            this.x = x;
            this.y = y;
            this.type = type;
            this.UUID = UUID;
            this.selectedShape = selectedShape;
            this.selectedShapeOptions = selectedShapeOptions;
        }
        CanvasWhiteboardUpdate.deserializeJson = function (json) {
            var parsedJson;
            try {
                parsedJson = JSON.parse(json);
                return new CanvasWhiteboardUpdate(parsedJson.x, parsedJson.y, parsedJson.type, parsedJson.uuid, parsedJson.selectedShape, parsedJson.selectedShapeOptions);
            }
            catch (e) {
                console.error('The canvas whiteboard update is not parseable');
                return null;
            }
        };
        CanvasWhiteboardUpdate.prototype.stringify = function () {
            var _a, _b;
            var objectToSerialize = {
                x: (_a = this.x) === null || _a === void 0 ? void 0 : _a.toFixed(3),
                y: (_b = this.y) === null || _b === void 0 ? void 0 : _b.toFixed(3),
                type: this.type,
                uuid: this.UUID,
                selectedShape: this.selectedShape,
                selectedShapeOptions: this.selectedShapeOptions || undefined
            };
            return JSON.stringify(objectToSerialize);
        };
        return CanvasWhiteboardUpdate;
    }());

    var CanvasWhiteboardService = /** @class */ (function () {
        function CanvasWhiteboardService() {
            this._canvasDrawSubject = new rxjs.Subject();
            this.canvasDrawSubject$ = this._canvasDrawSubject.asObservable();
            this._canvasClearSubject = new rxjs.Subject();
            this.canvasClearSubject$ = this._canvasClearSubject.asObservable();
            this._canvasUndoSubject = new rxjs.Subject();
            this.canvasUndoSubject$ = this._canvasUndoSubject.asObservable();
            this._canvasRedoSubject = new rxjs.Subject();
            this.canvasRedoSubject$ = this._canvasRedoSubject.asObservable();
        }
        CanvasWhiteboardService.prototype.drawCanvas = function (updates) {
            this._canvasDrawSubject.next(updates);
        };
        CanvasWhiteboardService.prototype.clearCanvas = function () {
            this._canvasClearSubject.next();
        };
        CanvasWhiteboardService.prototype.undoCanvas = function (updateUUD) {
            this._canvasUndoSubject.next(updateUUD);
        };
        CanvasWhiteboardService.prototype.redoCanvas = function (updateUUD) {
            this._canvasRedoSubject.next(updateUUD);
        };
        return CanvasWhiteboardService;
    }());
    CanvasWhiteboardService.decorators = [
        { type: core.Injectable }
    ];

    var CanvasWhiteboardPoint = /** @class */ (function () {
        function CanvasWhiteboardPoint(x, y) {
            this.x = x;
            this.y = y;
        }
        return CanvasWhiteboardPoint;
    }());

    var CanvasWhiteboardShapeOptions = /** @class */ (function () {
        function CanvasWhiteboardShapeOptions() {
            this.shouldFillShape = false;
            this.fillStyle = undefined;
            this.strokeStyle = 'rgba(0, 0, 0, 1)';
            this.lineWidth = 2;
            this.lineJoin = 'round';
            this.lineCap = 'round';
        }
        return CanvasWhiteboardShapeOptions;
    }());

    var CanvasWhiteboardShape = /** @class */ (function () {
        function CanvasWhiteboardShape(positionPoint, options) {
            this.positionPoint = positionPoint || new CanvasWhiteboardPoint(0, 0);
            this.options = options || new CanvasWhiteboardShapeOptions();
            this.isVisible = true;
        }
        // noinspection TsLint
        CanvasWhiteboardShape.prototype.onStopReceived = function (update) {
        };
        return CanvasWhiteboardShape;
    }());

    var CircleShape = /** @class */ (function (_super) {
        __extends(CircleShape, _super);
        function CircleShape(positionPoint, options, radius) {
            var _this = _super.call(this, positionPoint, options) || this;
            _this.radius = radius || 0;
            return _this;
        }
        CircleShape.prototype.getShapeName = function () {
            return 'CircleShape';
        };
        CircleShape.prototype.draw = function (context) {
            context.beginPath();
            context.arc(this.positionPoint.x, this.positionPoint.y, this.radius, 0, Math.PI * 2, false);
            Object.assign(context, this.options);
            context.stroke();
            if (this.options.shouldFillShape) {
                context.fill();
            }
            context.closePath();
        };
        CircleShape.prototype.drawPreview = function (context) {
            this.positionPoint = new CanvasWhiteboardPoint(context.canvas.width / 2, context.canvas.height / 2);
            this.radius = this.calculateRadius(context.canvas.width - 2, context.canvas.height / 2);
            this.draw(context);
        };
        CircleShape.prototype.onUpdateReceived = function (update) {
            this.radius = this.calculateRadius(update.x, update.y);
        };
        CircleShape.prototype.calculateRadius = function (x, y) {
            return Math.sqrt(Math.pow(x - this.positionPoint.x, 2) + Math.pow(y - this.positionPoint.y, 2));
        };
        return CircleShape;
    }(CanvasWhiteboardShape));

    var RectangleShape = /** @class */ (function (_super) {
        __extends(RectangleShape, _super);
        function RectangleShape(positionPoint, options, width, height) {
            var _this = _super.call(this, positionPoint, options) || this;
            _this.width = width || 0;
            _this.height = height || 0;
            return _this;
        }
        RectangleShape.prototype.getShapeName = function () {
            return 'RectangleShape';
        };
        RectangleShape.prototype.draw = function (context) {
            if (!this.width || !this.height) {
                return;
            }
            context.beginPath();
            Object.assign(context, this.options);
            context.rect(this.positionPoint.x, this.positionPoint.y, this.width, this.height);
            context.stroke();
            if (this.options.shouldFillShape) {
                context.fill();
            }
            context.closePath();
        };
        RectangleShape.prototype.drawPreview = function (context) {
            this.positionPoint = new CanvasWhiteboardPoint(2, 2);
            this.width = context.canvas.width - 4;
            this.height = context.canvas.height - 4;
            this.draw(context);
        };
        RectangleShape.prototype.onUpdateReceived = function (update) {
            this.width = update.x - this.positionPoint.x;
            this.height = update.y - this.positionPoint.y;
        };
        return RectangleShape;
    }(CanvasWhiteboardShape));

    var FreeHandShape = /** @class */ (function (_super) {
        __extends(FreeHandShape, _super);
        function FreeHandShape(positionPoint, options) {
            var _this = _super.call(this, positionPoint, options) || this;
            _this.linePositions = [];
            return _this;
        }
        FreeHandShape.prototype.getShapeName = function () {
            return 'FreeHandShape';
        };
        FreeHandShape.prototype.draw = function (context) {
            Object.assign(context, this.options);
            context.beginPath();
            context.moveTo(this.positionPoint.x, this.positionPoint.y);
            // Draw a dot
            context.lineTo(this.positionPoint.x + 1, this.positionPoint.y + 1);
            // Normal fastest free hand drawing
            // this.linePositions.forEach((linePosition) => {
            //     context.lineTo(linePosition.x, linePosition.y);
            // });
            // Quadratic curves drawing
            var i = 0;
            while (i < this.linePositions.length) {
                if (this.linePositions.length - i > 2) {
                    var controlPoint1 = this.linePositions[i];
                    var controlPoint2 = this.linePositions[i + 1];
                    var endPoint = this.linePositions[i + 2];
                    context.bezierCurveTo(controlPoint1.x, controlPoint1.y, controlPoint2.x, controlPoint2.y, endPoint.x, endPoint.y);
                    i += 2;
                }
                else {
                    var linePosition = this.linePositions[i];
                    context.lineTo(linePosition.x, linePosition.y);
                    i += 1;
                }
            }
            context.stroke();
        };
        FreeHandShape.prototype.drawPreview = function (context) {
            this.positionPoint = new CanvasWhiteboardPoint(2, 2);
            this.linePositions = [
                new CanvasWhiteboardPoint(context.canvas.width - 5, context.canvas.height * 0.3),
                // new CanvasWhiteboardPoint(context.canvas.width * 0.4, context.canvas.height * 0.6),
                new CanvasWhiteboardPoint(context.canvas.width * 0.2, context.canvas.height * 0.4),
                new CanvasWhiteboardPoint(context.canvas.width * 0.6, context.canvas.height * 0.8),
                new CanvasWhiteboardPoint(context.canvas.width, context.canvas.height)
            ];
            this.draw(context);
        };
        FreeHandShape.prototype.onUpdateReceived = function (update) {
            this.linePositions.push(new CanvasWhiteboardPoint(update.x, update.y));
        };
        return FreeHandShape;
    }(CanvasWhiteboardShape));

    var SmileyShape = /** @class */ (function (_super) {
        __extends(SmileyShape, _super);
        function SmileyShape(positionPoint, options, radius) {
            var _this = _super.call(this, positionPoint, options) || this;
            _this.options.shouldFillShape = true;
            _this.options.fillStyle = _this.options.fillStyle || 'yellow';
            _this.radius = radius || 0;
            return _this;
        }
        SmileyShape.prototype.getShapeName = function () {
            return 'SmileyShape';
        };
        SmileyShape.prototype.draw = function (context) {
            context.beginPath();
            Object.assign(context, this.options);
            context.arc(this.positionPoint.x, this.positionPoint.y, this.radius, 0, Math.PI * 2, false);
            context.fill();
            context.stroke();
            context.beginPath();
            var leftEyeX = this.positionPoint.x - this.radius * 0.3;
            var rightEyeX = this.positionPoint.x + this.radius * 0.3;
            var eyesY = this.positionPoint.y - this.radius * 0.2;
            var eyeSize = this.radius * 0.1;
            context.arc(leftEyeX, eyesY, eyeSize, 0, 2 * Math.PI, false);
            context.arc(rightEyeX, eyesY, eyeSize, 0, 2 * Math.PI, false);
            context.fillStyle = this.options.strokeStyle;
            context.fill();
            // draw the mouth
            context.beginPath();
            context.arc(this.positionPoint.x, this.positionPoint.y, this.radius * 0.7, 0, Math.PI, false);
            context.stroke();
            context.closePath();
        };
        SmileyShape.prototype.drawPreview = function (context) {
            this.positionPoint = new CanvasWhiteboardPoint(context.canvas.width / 2, context.canvas.height / 2);
            this.radius = this.calculateRadius(context.canvas.width - 2, context.canvas.height / 2);
            this.draw(context);
        };
        SmileyShape.prototype.onUpdateReceived = function (update) {
            this.radius = this.calculateRadius(update.x, update.y);
        };
        SmileyShape.prototype.calculateRadius = function (x, y) {
            return Math.sqrt(Math.pow(x - this.positionPoint.x, 2) + Math.pow(y - this.positionPoint.y, 2));
        };
        return SmileyShape;
    }(CanvasWhiteboardShape));

    var StarShape = /** @class */ (function (_super) {
        __extends(StarShape, _super);
        function StarShape(positionPoint, options, radius, spikes) {
            var _this = _super.call(this, positionPoint, options) || this;
            _this.radius = radius || 0;
            _this.spikes = spikes || 5;
            return _this;
        }
        StarShape.prototype.getShapeName = function () {
            return 'StarShape';
        };
        StarShape.prototype.draw = function (context) {
            Object.assign(context, this.options);
            var rotation = Math.PI / 2 * 3;
            var spikeX = this.positionPoint.x;
            var spikeY = this.positionPoint.y;
            var step = Math.PI / this.spikes;
            context.beginPath();
            context.moveTo(this.positionPoint.x, this.positionPoint.y - this.radius);
            for (var i = 0; i < this.spikes; i++) {
                spikeX = this.positionPoint.x + Math.cos(rotation) * this.radius;
                spikeY = this.positionPoint.y + Math.sin(rotation) * this.radius;
                context.lineTo(spikeX, spikeY);
                rotation += step;
                spikeX = this.positionPoint.x + Math.cos(rotation) * (this.radius * 0.4);
                spikeY = this.positionPoint.y + Math.sin(rotation) * (this.radius * 0.4);
                context.lineTo(spikeX, spikeY);
                rotation += step;
                context.stroke();
            }
            context.lineTo(this.positionPoint.x, this.positionPoint.y - this.radius);
            context.closePath();
            context.stroke();
            if (this.options.shouldFillShape) {
                context.fill();
            }
        };
        StarShape.prototype.drawPreview = function (context) {
            this.positionPoint = new CanvasWhiteboardPoint(context.canvas.width / 2, context.canvas.height / 2);
            this.radius = this.calculateRadius(context.canvas.width - 2, context.canvas.height / 2);
            this.draw(context);
        };
        StarShape.prototype.onUpdateReceived = function (update) {
            this.radius = this.calculateRadius(update.x, update.y);
        };
        StarShape.prototype.calculateRadius = function (x, y) {
            return Math.sqrt(Math.pow(x - this.positionPoint.x, 2) + Math.pow(y - this.positionPoint.y, 2));
        };
        return StarShape;
    }(CanvasWhiteboardShape));

    var LineShape = /** @class */ (function (_super) {
        __extends(LineShape, _super);
        function LineShape(positionPoint, options, endPosition) {
            var _this = _super.call(this, positionPoint, options) || this;
            _this.endPosition = endPosition || new CanvasWhiteboardPoint(_this.positionPoint.x, _this.positionPoint.y);
            return _this;
        }
        LineShape.prototype.getShapeName = function () {
            return 'LineShape';
        };
        LineShape.prototype.draw = function (context) {
            if (!this.endPosition) {
                return;
            }
            context.beginPath();
            Object.assign(context, this.options);
            context.moveTo(this.positionPoint.x, this.positionPoint.y);
            context.lineTo(this.endPosition.x, this.endPosition.y);
            context.closePath();
            context.stroke();
        };
        LineShape.prototype.drawPreview = function (context) {
            this.positionPoint = new CanvasWhiteboardPoint(0, 0);
            this.endPosition = new CanvasWhiteboardPoint(context.canvas.width, context.canvas.height);
            this.draw(context);
        };
        LineShape.prototype.onUpdateReceived = function (update) {
            this.endPosition = new CanvasWhiteboardPoint(update.x, update.y);
        };
        return LineShape;
    }(CanvasWhiteboardShape));

    var CanvasWhiteboardShapeService = /** @class */ (function () {
        function CanvasWhiteboardShapeService() {
            this.registeredShapesSubject = new rxjs.BehaviorSubject([
                FreeHandShape,
                LineShape,
                RectangleShape,
                CircleShape,
                StarShape,
                SmileyShape
            ]);
            this.registeredShapes$ = this.registeredShapesSubject.asObservable();
        }
        CanvasWhiteboardShapeService.prototype.getShapeConstructorFromShapeName = function (shapeName) {
            return this.getCurrentRegisteredShapes().find(function (shape) { return (new shape()).getShapeName() === shapeName; });
        };
        CanvasWhiteboardShapeService.prototype.getCurrentRegisteredShapes = function () {
            return this.registeredShapesSubject.getValue();
        };
        CanvasWhiteboardShapeService.prototype.isRegisteredShape = function (shape) {
            return this.getCurrentRegisteredShapes().indexOf(shape) !== -1;
        };
        CanvasWhiteboardShapeService.prototype.registerShape = function (shape) {
            if (this.isRegisteredShape(shape)) {
                console.warn("You tried to register a shape:" + shape + ", but is has already been registered.");
                return;
            }
            var registeredShapes = this.getCurrentRegisteredShapes();
            registeredShapes.push(shape);
            this.registeredShapesSubject.next(registeredShapes);
        };
        CanvasWhiteboardShapeService.prototype.registerShapes = function (shapes) {
            var _this = this;
            this.registeredShapesSubject.next(this.getCurrentRegisteredShapes()
                .concat(shapes.filter(function (shape) {
                if (_this.isRegisteredShape(shape)) {
                    console.warn("You tried to register a shape:" + shape + ", but is has already been registered.");
                    return false;
                }
                return true;
            })));
        };
        CanvasWhiteboardShapeService.prototype.unregisterShape = function (shape) {
            this.registeredShapesSubject.next(this.getCurrentRegisteredShapes().filter(function (registeredShape) { return registeredShape !== shape; }));
        };
        CanvasWhiteboardShapeService.prototype.unregisterShapes = function (shapes) {
            this.registeredShapesSubject.next(this.getCurrentRegisteredShapes().filter(function (shape) { return shapes.indexOf(shape) === -1; }));
        };
        return CanvasWhiteboardShapeService;
    }());
    CanvasWhiteboardShapeService.decorators = [
        { type: core.Injectable }
    ];
    CanvasWhiteboardShapeService.ctorParameters = function () { return []; };

    var CanvasWhiteboardComponent = /** @class */ (function () {
        function CanvasWhiteboardComponent(ngZone, changeDetectorRef, canvasWhiteboardService, canvasWhiteboardShapeService) {
            this.ngZone = ngZone;
            this.changeDetectorRef = changeDetectorRef;
            this.canvasWhiteboardService = canvasWhiteboardService;
            this.canvasWhiteboardShapeService = canvasWhiteboardShapeService;
            // Number of ms to wait before sending out the updates as an array
            this.batchUpdateTimeoutDuration = 100;
            this.drawButtonText = '';
            this.clearButtonText = '';
            this.eraseButtonText = '';
            this.undoButtonText = '';
            this.redoButtonText = '';
            this.saveDataButtonText = '';
            this.strokeColorPickerText = 'Stroke';
            this.fillColorPickerText = 'Fill';
            this.drawButtonEnabled = true;
            this.clearButtonEnabled = true;
            this.eraseButtonEnabled = true;
            this.undoButtonEnabled = false;
            this.redoButtonEnabled = false;
            this.saveDataButtonEnabled = false;
            this.shouldDownloadDrawing = true;
            this.strokeColorPickerEnabled = false;
            this.fillColorPickerEnabled = false;
            this.lineWidth = 2;
            this.eraserLineWidth = 10;
            this.strokeColor = 'rgba(0, 0, 0, 1)';
            this.startingColor = '#fff';
            this.scaleFactor = 0;
            this.drawingEnabled = false;
            this.showStrokeColorPicker = false;
            this.showFillColorPicker = false;
            this.lineJoin = 'round';
            this.lineCap = 'round';
            this.shapeSelectorEnabled = true;
            this.showShapeSelector = false;
            this.fillColor = 'rgba(0,0,0,0)';
            this.clear = new core.EventEmitter();
            this.undo = new core.EventEmitter();
            this.redo = new core.EventEmitter();
            this.batchUpdate = new core.EventEmitter();
            this.imageLoaded = new core.EventEmitter();
            this.save = new core.EventEmitter();
            this.cachedStrokeColor = '';
            this._canDraw = true;
            this._erasingEnabled = false;
            this._clientDragging = false;
            this._updateHistory = [];
            this._undoStack = []; // Stores the value of start and count for each continuous stroke
            this._redoStack = [];
            this._batchUpdates = [];
            this._updatesNotDrawn = [];
            this._canvasWhiteboardServiceSubscriptions = [];
            this._shapesMap = new Map();
            this._incompleteShapesMap = new Map();
            this.canvasWhiteboardShapePreviewOptions = this.generateShapePreviewOptions();
        }
        Object.defineProperty(CanvasWhiteboardComponent.prototype, "imageUrl", {
            get: function () {
                return this._imageUrl;
            },
            set: function (imageUrl) {
                this._imageUrl = imageUrl;
                this._imageElement = null;
                this._redrawHistory();
            },
            enumerable: false,
            configurable: true
        });
        Object.defineProperty(CanvasWhiteboardComponent.prototype, "erasingEnabled", {
            // tslint:disable-next-line:adjacent-overload-signatures
            get: function () {
                return this._erasingEnabled;
            },
            set: function (value) {
                this._erasingEnabled = value;
                this._setErasing(value);
            },
            enumerable: false,
            configurable: true
        });
        /**
         * Initialize the canvas drawing context. If we have an aspect ratio set up, the canvas will resize
         * according to the aspect ratio.
         */
        CanvasWhiteboardComponent.prototype.ngOnInit = function () {
            this._initInputsFromOptions(this.options);
            this._initCanvasEventListeners();
            this._initCanvasServiceObservables();
            this.context = this.canvas.nativeElement.getContext('2d');
            this._incompleteShapesCanvasContext = this._incompleteShapesCanvas.nativeElement.getContext('2d');
        };
        /**
         * If an image exists and it's url changes, we need to redraw the new image on the canvas.
         */
        CanvasWhiteboardComponent.prototype.ngOnChanges = function (changes) {
            if (changes.options && !lodashEs.isEqual(changes.options.currentValue, changes.options.previousValue)) {
                this._initInputsFromOptions(changes.options.currentValue);
            }
        };
        /**
         * Recalculate the width and height of the canvas after the view has been fully initialized
         */
        CanvasWhiteboardComponent.prototype.ngAfterViewInit = function () {
            this._calculateCanvasWidthAndHeight();
            this._redrawHistory();
        };
        /**
         * Unsubscribe from the service observables
         */
        CanvasWhiteboardComponent.prototype.ngOnDestroy = function () {
            var _this = this;
            this._unsubscribe(this._resizeSubscription);
            this._unsubscribe(this._registeredShapesSubscription);
            this._canvasWhiteboardServiceSubscriptions.forEach(function (subscription) { return _this._unsubscribe(subscription); });
        };
        /**
         * This method reads the options which are helpful since they can be really long when specified in HTML
         * This method is also called everytime the options object changes
         * For security reasons we must check each item on its own since if we iterate the keys
         * we may be injected with malicious values
         */
        CanvasWhiteboardComponent.prototype._initInputsFromOptions = function (options) {
            if (options) {
                if (!this._isNullOrUndefined(options.eraserLineWidth)) {
                    this.eraserLineWidth = options.eraserLineWidth;
                }
                if (!this._isNullOrUndefined(options.eraseButtonClass)) {
                    this.eraseButtonClass = options.eraseButtonClass;
                }
                if (!this._isNullOrUndefined(options.eraseButtonText)) {
                    this.eraseButtonText = options.eraseButtonText;
                }
                if (!this._isNullOrUndefined(options.erasingEnabled)) {
                    this.erasingEnabled = options.erasingEnabled;
                }
                if (!this._isNullOrUndefined(options.customWhiteboardUi)) {
                    this.customWhiteboardUi = options.customWhiteboardUi;
                }
                if (!this._isNullOrUndefined(options.batchUpdateTimeoutDuration)) {
                    this.batchUpdateTimeoutDuration = options.batchUpdateTimeoutDuration;
                }
                if (!this._isNullOrUndefined(options.imageUrl)) {
                    this.imageUrl = options.imageUrl;
                }
                if (!this._isNullOrUndefined(options.aspectRatio)) {
                    this.aspectRatio = options.aspectRatio;
                }
                if (!this._isNullOrUndefined(options.drawButtonClass)) {
                    this.drawButtonClass = options.drawButtonClass;
                }
                if (!this._isNullOrUndefined(options.clearButtonClass)) {
                    this.clearButtonClass = options.clearButtonClass;
                }
                if (!this._isNullOrUndefined(options.undoButtonClass)) {
                    this.undoButtonClass = options.undoButtonClass;
                }
                if (!this._isNullOrUndefined(options.redoButtonClass)) {
                    this.redoButtonClass = options.redoButtonClass;
                }
                if (!this._isNullOrUndefined(options.saveDataButtonClass)) {
                    this.saveDataButtonClass = options.saveDataButtonClass;
                }
                if (!this._isNullOrUndefined(options.drawButtonText)) {
                    this.drawButtonText = options.drawButtonText;
                }
                if (!this._isNullOrUndefined(options.clearButtonText)) {
                    this.clearButtonText = options.clearButtonText;
                }
                if (!this._isNullOrUndefined(options.undoButtonText)) {
                    this.undoButtonText = options.undoButtonText;
                }
                if (!this._isNullOrUndefined(options.redoButtonText)) {
                    this.redoButtonText = options.redoButtonText;
                }
                if (!this._isNullOrUndefined(options.saveDataButtonText)) {
                    this.saveDataButtonText = options.saveDataButtonText;
                }
                if (!this._isNullOrUndefined(options.strokeColorPickerText)) {
                    this.strokeColorPickerText = options.strokeColorPickerText;
                }
                if (!this._isNullOrUndefined(options.fillColorPickerText)) {
                    this.fillColorPickerText = options.fillColorPickerText;
                }
                if (!this._isNullOrUndefined(options.drawButtonEnabled)) {
                    this.drawButtonEnabled = options.drawButtonEnabled;
                }
                if (!this._isNullOrUndefined(options.clearButtonEnabled)) {
                    this.clearButtonEnabled = options.clearButtonEnabled;
                }
                if (!this._isNullOrUndefined(options.undoButtonEnabled)) {
                    this.undoButtonEnabled = options.undoButtonEnabled;
                }
                if (!this._isNullOrUndefined(options.redoButtonEnabled)) {
                    this.redoButtonEnabled = options.redoButtonEnabled;
                }
                if (!this._isNullOrUndefined(options.saveDataButtonEnabled)) {
                    this.saveDataButtonEnabled = options.saveDataButtonEnabled;
                }
                if (!this._isNullOrUndefined(options.strokeColorPickerEnabled)) {
                    this.strokeColorPickerEnabled = options.strokeColorPickerEnabled;
                }
                if (!this._isNullOrUndefined(options.fillColorPickerEnabled)) {
                    this.fillColorPickerEnabled = options.fillColorPickerEnabled;
                }
                if (!this._isNullOrUndefined(options.lineWidth)) {
                    this.lineWidth = options.lineWidth;
                }
                if (!this._isNullOrUndefined(options.strokeColor)) {
                    this.strokeColor = options.strokeColor;
                }
                if (!this._isNullOrUndefined(options.shouldDownloadDrawing)) {
                    this.shouldDownloadDrawing = options.shouldDownloadDrawing;
                }
                if (!this._isNullOrUndefined(options.startingColor)) {
                    this.startingColor = options.startingColor;
                }
                if (!this._isNullOrUndefined(options.scaleFactor)) {
                    this.scaleFactor = options.scaleFactor;
                }
                if (!this._isNullOrUndefined(options.drawingEnabled)) {
                    this.drawingEnabled = options.drawingEnabled;
                }
                if (!this._isNullOrUndefined(options.downloadedFileName)) {
                    this.downloadedFileName = options.downloadedFileName;
                }
                if (!this._isNullOrUndefined(options.lineJoin)) {
                    this.lineJoin = options.lineJoin;
                }
                if (!this._isNullOrUndefined(options.lineCap)) {
                    this.lineCap = options.lineCap;
                }
                if (!this._isNullOrUndefined(options.shapeSelectorEnabled)) {
                    this.shapeSelectorEnabled = options.shapeSelectorEnabled;
                }
                if (!this._isNullOrUndefined(options.showShapeSelector)) {
                    this.showShapeSelector = options.showShapeSelector;
                }
                if (!this._isNullOrUndefined(options.fillColor)) {
                    this.fillColor = options.fillColor;
                }
                if (!this._isNullOrUndefined(options.showStrokeColorPicker)) {
                    this.showStrokeColorPicker = options.showStrokeColorPicker;
                }
                if (!this._isNullOrUndefined(options.showFillColorPicker)) {
                    this.showFillColorPicker = options.showFillColorPicker;
                }
            }
        };
        // noinspection JSMethodCanBeStatic
        CanvasWhiteboardComponent.prototype._isNullOrUndefined = function (property) {
            return property === null || property === undefined;
        };
        /**
         * Init global window listeners like resize and keydown
         */
        CanvasWhiteboardComponent.prototype._initCanvasEventListeners = function () {
            var _this = this;
            this.ngZone.runOutsideAngular(function () {
                _this._resizeSubscription = rxjs.fromEvent(window, 'resize')
                    .pipe(operators.debounceTime(200), operators.distinctUntilChanged()).subscribe(function () {
                    _this.ngZone.run(function () {
                        _this._redrawCanvasOnResize();
                    });
                });
            });
            window.addEventListener('keydown', this._canvasKeyDown.bind(this), false);
        };
        /**
         * Subscribes to new signals in the canvas whiteboard service and executes methods accordingly
         * Because of circular publishing and subscribing, the canvas methods do not use the service when
         * local actions are completed (Ex. clicking undo from the button inside this component)
         */
        CanvasWhiteboardComponent.prototype._initCanvasServiceObservables = function () {
            var _this = this;
            this._canvasWhiteboardServiceSubscriptions.push(this.canvasWhiteboardService.canvasDrawSubject$
                .subscribe(function (updates) { return _this.drawUpdates(updates); }));
            this._canvasWhiteboardServiceSubscriptions.push(this.canvasWhiteboardService.canvasClearSubject$
                .subscribe(function () { return _this.clearCanvas(); }));
            this._canvasWhiteboardServiceSubscriptions.push(this.canvasWhiteboardService.canvasUndoSubject$
                .subscribe(function (updateUUD) { return _this._undoCanvas(updateUUD); }));
            this._canvasWhiteboardServiceSubscriptions.push(this.canvasWhiteboardService.canvasRedoSubject$
                .subscribe(function (updateUUD) { return _this._redoCanvas(updateUUD); }));
            this._registeredShapesSubscription = this.canvasWhiteboardShapeService.registeredShapes$.subscribe(function (shapes) {
                if (!_this.selectedShapeConstructor || !_this.canvasWhiteboardShapeService.isRegisteredShape(_this.selectedShapeConstructor)) {
                    _this.selectedShapeConstructor = shapes[0];
                }
            });
        };
        /**
         * Calculate the canvas width and height from it's parent container width and height (use aspect ratio if needed)
         */
        CanvasWhiteboardComponent.prototype._calculateCanvasWidthAndHeight = function () {
            this.context.canvas.width = this.canvas.nativeElement.parentNode.clientWidth;
            if (this.aspectRatio) {
                this.context.canvas.height = this.canvas.nativeElement.parentNode.clientWidth * this.aspectRatio;
            }
            else {
                this.context.canvas.height = this.canvas.nativeElement.parentNode.clientHeight;
            }
            this._incompleteShapesCanvasContext.canvas.width = this.context.canvas.width;
            this._incompleteShapesCanvasContext.canvas.height = this.context.canvas.height;
        };
        /**
         * Load an image and draw it on the canvas (if an image exists)
         * @param callbackFn A function that is called after the image loading is finished
         * @return Emits a value when the image has been loaded.
         */
        CanvasWhiteboardComponent.prototype._loadImage = function (callbackFn) {
            var _this = this;
            this._canDraw = false;
            // If we already have the image there is no need to acquire it
            if (this._imageElement) {
                this._canDraw = true;
                if (callbackFn)
                    callbackFn();
                return;
            }
            this._imageElement = new Image();
            this._imageElement.addEventListener('load', function () {
                _this._canDraw = true;
                if (callbackFn)
                    callbackFn();
                _this.imageLoaded.emit(true);
            });
            this._imageElement.src = this.imageUrl;
        };
        /**
         * Sends a notification after clearing the canvas
         * This method should only be called from the clear button in this component since it will emit an clear event
         * If the client calls this method he may create a circular clear action which may cause danger.
         */
        CanvasWhiteboardComponent.prototype.clearCanvasLocal = function () {
            this.clearCanvas();
            this.clear.emit(true);
        };
        /**
         * Clears all content on the canvas.
         */
        CanvasWhiteboardComponent.prototype.clearCanvas = function () {
            this._removeCanvasData();
            this._redoStack = [];
        };
        /**
         * This method resets the state of the canvas and redraws it.
         * It calls a callback function after redrawing
         */
        CanvasWhiteboardComponent.prototype._removeCanvasData = function (callbackFn) {
            this._shapesMap = new Map();
            this._clientDragging = false;
            this._updateHistory = [];
            this._undoStack = [];
            this._redrawBackground(callbackFn);
        };
        /**
         * Clears the canvas and redraws the image if the url exists.
         * @param callbackFn A function that is called after the background is redrawn
         * @return Emits a value when the clearing is finished
         */
        CanvasWhiteboardComponent.prototype._redrawBackground = function (callbackFn) {
            var _this = this;
            if (this.context) {
                if (this.imageUrl) {
                    this._loadImage(function () {
                        _this.context.save();
                        _this._drawImage(_this.context, _this._imageElement, 0, 0, _this.context.canvas.width, _this.context.canvas.height, 0.5, 0.5);
                        _this.context.restore();
                        _this._drawMissingUpdates();
                        if (callbackFn)
                            callbackFn();
                    });
                }
                else {
                    this.context.clearRect(0, 0, this.context.canvas.width, this.context.canvas.height);
                    this._drawStartingColor();
                    if (callbackFn)
                        callbackFn();
                }
            }
        };
        CanvasWhiteboardComponent.prototype._drawStartingColor = function () {
            var previousFillStyle = this.context.fillStyle;
            this.context.save();
            this.context.fillStyle = this.startingColor;
            this.context.fillRect(0, 0, this.context.canvas.width, this.context.canvas.height);
            this.context.fillStyle = previousFillStyle;
            this.context.restore();
        };
        /**
         * Returns a value of whether the user clicked the draw button on the canvas.
         */
        CanvasWhiteboardComponent.prototype.getDrawingEnabled = function () {
            return this.drawingEnabled;
        };
        /**
         * Toggles drawing on the canvas. It is called via the draw button on the canvas.
         */
        CanvasWhiteboardComponent.prototype.toggleDrawingEnabled = function () {
            this.drawingEnabled = !this.drawingEnabled;
            if (this.drawingEnabled && this.erasingEnabled) {
                this.erasingEnabled = false;
            }
        };
        /**
         * Set if drawing is enabled from the client using the canvas
         */
        CanvasWhiteboardComponent.prototype.setDrawingEnabled = function (drawingEnabled) {
            this.drawingEnabled = drawingEnabled;
            if (this.drawingEnabled && this.erasingEnabled) {
                this.erasingEnabled = false;
            }
        };
        /**
         * Toggles erasing on the canvas. It is called via the erase button on the canvas.
         */
        CanvasWhiteboardComponent.prototype.toggleErasingEnabled = function () {
            this.erasingEnabled = !this._erasingEnabled;
            if (this.drawingEnabled && this.erasingEnabled) {
                this.setDrawingEnabled(false);
            }
        };
        /**
         * Replaces the drawing color with a new color
         * The format should be ("#ffffff" or "rgb(r,g,b,a?)")
         * This method is public so that anyone can access the canvas and change the stroke color
         *
         * @param newStrokeColor The new stroke color
         */
        CanvasWhiteboardComponent.prototype.changeStrokeColor = function (newStrokeColor) {
            this.strokeColor = newStrokeColor;
            this.canvasWhiteboardShapePreviewOptions = this.generateShapePreviewOptions();
            this.changeDetectorRef.detectChanges();
        };
        /**
         * Replaces the fill color with a new color
         * The format should be ("#ffffff" or "rgb(r,g,b,a?)")
         * This method is public so that anyone can access the canvas and change the fill color
         *
         * @param newFillColor The new fill color
         */
        CanvasWhiteboardComponent.prototype.changeFillColor = function (newFillColor) {
            this.fillColor = newFillColor;
            this.canvasWhiteboardShapePreviewOptions = this.generateShapePreviewOptions();
            this.changeDetectorRef.detectChanges();
        };
        /**
         * This method is invoked by the undo button on the canvas screen
         * It calls the global undo method and emits a notification after undoing.
         * This method should only be called from the undo button in this component since it will emit an undo event
         * If the client calls this method he may create a circular undo action which may cause danger.
         */
        CanvasWhiteboardComponent.prototype.undoLocal = function () {
            var _this = this;
            this.doUndo(function (updateUUID) {
                _this._redoStack.push(updateUUID);
                _this.undo.emit(updateUUID);
            });
        };
        /**
         * This methods selects the last uuid prepares it for undoing (making the whole update sequence invisible)
         * This method can be called if the canvas component is a ViewChild of some other component.
         * This method will work even if the undo button has been disabled
         */
        CanvasWhiteboardComponent.prototype.doUndo = function (callbackFn) {
            if (!this._undoStack.length) {
                return;
            }
            // FIXME is casting to string correct here?
            var updateUUID = this._undoStack.pop();
            this._undoCanvas(updateUUID);
            if (callbackFn)
                callbackFn(updateUUID);
        };
        /**
         * This method takes an UUID for an update, and redraws the canvas by making all updates with that uuid invisible
         */
        CanvasWhiteboardComponent.prototype._undoCanvas = function (updateUUID) {
            if (this._shapesMap.has(updateUUID)) {
                var shape = this._shapesMap.get(updateUUID);
                if (shape)
                    shape.isVisible = false;
                this.drawAllShapes();
            }
        };
        /**
         * This method is invoked by the redo button on the canvas screen
         * It calls the global redo method and emits a notification after redoing
         * This method should only be called from the redo button in this component since it will emit an redo event
         * If the client calls this method he may create a circular redo action which may cause danger.
         */
        CanvasWhiteboardComponent.prototype.redoLocal = function () {
            var _this = this;
            this.doRedo(function (updateUUID) {
                _this._undoStack.push(updateUUID);
                _this.redo.emit(updateUUID);
            });
        };
        /**
         * This methods selects the last uuid prepares it for redoing (making the whole update sequence visible)
         * This method can be called if the canvas component is a ViewChild of some other component.
         * This method will work even if the redo button has been disabled
         */
        CanvasWhiteboardComponent.prototype.doRedo = function (callbackFn) {
            if (!this._redoStack.length) {
                return;
            }
            // FIXME is casting to string correct here?
            var updateUUID = this._redoStack.pop();
            this._redoCanvas(updateUUID);
            if (callbackFn)
                callbackFn(updateUUID);
        };
        /**
         * This method takes an UUID for an update, and redraws the canvas by making all updates with that uuid visible
         */
        CanvasWhiteboardComponent.prototype._redoCanvas = function (updateUUID) {
            if (this._shapesMap.has(updateUUID)) {
                var shape = this._shapesMap.get(updateUUID);
                if (shape)
                    shape.isVisible = true;
                this.drawAllShapes();
            }
        };
        /**
         * Catches the Mouse and Touch events made on the canvas.
         * If drawing is disabled (If an image exists but it's not loaded, or the user did not click Draw),
         * this function does nothing.
         *
         * If a "mousedown | touchstart" event is triggered, dragging will be set to true and an CanvasWhiteboardUpdate object
         * of type "start" will be drawn and then sent as an update to all receiving ends.
         *
         * If a "mousemove | touchmove" event is triggered and the client is dragging, an CanvasWhiteboardUpdate object
         * of type "drag" will be drawn and then sent as an update to all receiving ends.
         *
         * If a "mouseup, mouseout | touchend, touchcancel" event is triggered, dragging will be set to false and
         * an CanvasWhiteboardUpdate object of type "stop" will be drawn and then sent as an update to all receiving ends.
         *
         */
        CanvasWhiteboardComponent.prototype.canvasUserEvents = function (event) {
            // Ignore all if we don't have drawing/erasing enabled or the image did not load
            if ((!this.drawingEnabled && !this._erasingEnabled) || !this._canDraw) {
                return;
            }
            // Ignore mouse move Events if we're not dragging
            if (!this._clientDragging
                && (event.type === 'mousemove'
                    || event.type === 'touchmove'
                    || event.type === 'mouseout'
                    || event.type === 'touchcancel'
                    || event.type === 'mouseup'
                    || event.type === 'touchend'
                    || event.type === 'mouseout')) {
                return;
            }
            // Ignore the middle mouse button
            if (event.button === 1) {
                return;
            }
            if (event.target === this._incompleteShapesCanvas.nativeElement || event.target === this.canvas.nativeElement) {
                event.preventDefault();
            }
            var update;
            var updateType;
            var eventPosition = this._getCanvasEventPosition(event);
            update = new CanvasWhiteboardUpdate(eventPosition.x, eventPosition.y);
            switch (event.type) {
                case 'mousedown':
                case 'touchstart':
                    this._clientDragging = true;
                    this._lastUUID = this._generateUUID();
                    updateType = CanvasWhiteboardUpdateType.START;
                    this._redoStack = [];
                    this._addCurrentShapeDataToAnUpdate(update);
                    break;
                case 'mousemove':
                case 'touchmove':
                    if (!this._clientDragging) {
                        return;
                    }
                    updateType = CanvasWhiteboardUpdateType.DRAG;
                    break;
                case 'touchcancel':
                case 'mouseup':
                case 'touchend':
                case 'mouseout':
                    this._clientDragging = false;
                    updateType = CanvasWhiteboardUpdateType.STOP;
                    this._undoStack.push(this._lastUUID);
                    break;
            }
            update.UUID = this._lastUUID;
            update.type = updateType;
            this._draw(update);
            this._prepareToSendUpdate(update);
        };
        /**
         * Get the coordinates (x,y) from a given event
         * If it is a touch event, get the touch positions
         * If we released the touch, the position will be placed in the changedTouches object
         * If it is not a touch event, use the original mouse event received
         */
        CanvasWhiteboardComponent.prototype._getCanvasEventPosition = function (eventData) {
            var canvasBoundingRect = this.context.canvas.getBoundingClientRect();
            var hasTouches = (eventData.touches && eventData.touches.length)
                ? eventData.touches[0]
                : null;
            if (!hasTouches) {
                hasTouches = (eventData.changedTouches && eventData.changedTouches.length)
                    ? eventData.changedTouches[0]
                    : null;
            }
            var event = hasTouches || eventData;
            var scaleWidth = canvasBoundingRect.width / this.context.canvas.width;
            var scaleHeight = canvasBoundingRect.height / this.context.canvas.height;
            var xPosition = (event.clientX - canvasBoundingRect.left);
            var yPosition = (event.clientY - canvasBoundingRect.top);
            xPosition /= this.scaleFactor || scaleWidth;
            yPosition /= this.scaleFactor || scaleHeight;
            return new CanvasWhiteboardPoint(xPosition / this.context.canvas.width, yPosition / this.context.canvas.height);
        };
        /**
         * The update coordinates on the canvas are mapped so that all receiving ends
         * can reverse the mapping and get the same position as the one that
         * was drawn on this update.
         *
         * @param update The CanvasWhiteboardUpdate object.
         */
        CanvasWhiteboardComponent.prototype._prepareToSendUpdate = function (update) {
            this._prepareUpdateForBatchDispatch(update);
        };
        /**
         * Catches the Key Up events made on the canvas.
         * If the ctrlKey or commandKey(macOS) was held and the keyCode is 90 (z), an undo action will be performed
         * If the ctrlKey or commandKey(macOS) was held and the keyCode is 89 (y), a redo action will be performed
         * If the ctrlKey or commandKey(macOS) was held and the keyCode is 83 (s) or 115(S), a save action will be performed
         *
         * @param event The event that occurred.
         */
        CanvasWhiteboardComponent.prototype._canvasKeyDown = function (event) {
            if (event.ctrlKey || event.metaKey) {
                if (event.keyCode === 90 && this.undoButtonEnabled) {
                    event.preventDefault();
                    this.doUndo();
                }
                if (event.keyCode === 89 && this.redoButtonEnabled) {
                    event.preventDefault();
                    this.doRedo();
                }
                if (event.keyCode === 83 || event.keyCode === 115) {
                    event.preventDefault();
                    this.saveLocal();
                }
            }
        };
        /**
         * On window resize, recalculate the canvas dimensions and redraw the history
         */
        CanvasWhiteboardComponent.prototype._redrawCanvasOnResize = function () {
            this._calculateCanvasWidthAndHeight();
            this._redrawHistory();
        };
        /**
         * Redraw the saved history after resetting the canvas state
         */
        CanvasWhiteboardComponent.prototype._redrawHistory = function () {
            var _this = this;
            var updatesToDraw = __spread(this._updateHistory);
            this._removeCanvasData(function () {
                updatesToDraw.forEach(function (update) {
                    _this._draw(update);
                });
            });
        };
        /**
         * Draws a CanvasWhiteboardUpdate object on the canvas.
         * The coordinates are first reverse mapped so that they can be drawn in the proper place. The update
         * is afterwards added to the undoStack so that it can be
         *
         * If the CanvasWhiteboardUpdate Type is "start", a new "selectedShape" is created.
         * If the CanvasWhiteboardUpdate Type is "drag", the shape is taken from the shapesMap and then it's updated.
         * Afterwards the context is used to draw the shape on the canvas.
         * This function saves the last X and Y coordinates that were drawn.
         *
         * @param update The update object.
         */
        CanvasWhiteboardComponent.prototype._draw = function (update) {
            this._updateHistory.push(update);
            // map the canvas coordinates to our canvas size since they are scaled.
            // FIXME should the x and y be casted to number?
            update = Object.assign(new CanvasWhiteboardUpdate(), update, {
                x: update.x * this.context.canvas.width,
                y: update.y * this.context.canvas.height
            });
            var shape;
            switch (update.type) {
                case CanvasWhiteboardUpdateType.START:
                    var updateShapeConstructor = this.canvasWhiteboardShapeService
                        .getShapeConstructorFromShapeName(update.selectedShape);
                    if (updateShapeConstructor) {
                        // FIXME should the x and y be casted to number?
                        shape = new updateShapeConstructor(new CanvasWhiteboardPoint(update.x, update.y), Object.assign(new CanvasWhiteboardShapeOptions(), update.selectedShapeOptions));
                    }
                    // FIXME should the UUID be casted to string?
                    this._incompleteShapesMap.set(update.UUID, shape);
                    this._drawIncompleteShapes();
                    break;
                case CanvasWhiteboardUpdateType.DRAG:
                    shape = this._incompleteShapesMap.get(update.UUID);
                    if (shape)
                        shape.onUpdateReceived(update);
                    this._drawIncompleteShapes();
                    break;
                case CanvasWhiteboardUpdateType.STOP:
                    shape = this._incompleteShapesMap.get(update.UUID);
                    shape.onStopReceived(update);
                    this._shapesMap.set(update.UUID, shape);
                    this._incompleteShapesMap.delete(update.UUID);
                    this._swapCompletedShapeToActualCanvas(shape);
                    break;
            }
        };
        CanvasWhiteboardComponent.prototype._drawIncompleteShapes = function () {
            var _this = this;
            this._resetIncompleteShapeCanvas();
            this._incompleteShapesMap.forEach(function (shape) {
                if (shape.isVisible) {
                    shape.draw(_this._incompleteShapesCanvasContext);
                }
            });
        };
        CanvasWhiteboardComponent.prototype._swapCompletedShapeToActualCanvas = function (shape) {
            this._drawIncompleteShapes();
            if (shape.isVisible) {
                shape.draw(this.context);
            }
        };
        CanvasWhiteboardComponent.prototype._resetIncompleteShapeCanvas = function () {
            this._incompleteShapesCanvasContext.clearRect(0, 0, this._incompleteShapesCanvasContext.canvas.width, this._incompleteShapesCanvasContext.canvas.height);
            this._incompleteShapesCanvasContext.fillStyle = 'transparent';
            this._incompleteShapesCanvasContext.fillRect(0, 0, this._incompleteShapesCanvasContext.canvas.width, this._incompleteShapesCanvasContext.canvas.height);
        };
        /**
         * Delete everything from the screen, redraw the background, and then redraw all the shapes from the shapesMap
         */
        CanvasWhiteboardComponent.prototype.drawAllShapes = function () {
            var _this = this;
            this._redrawBackground(function () {
                _this._shapesMap.forEach(function (shape) {
                    if (shape.isVisible) {
                        shape.draw(_this.context);
                    }
                });
            });
        };
        CanvasWhiteboardComponent.prototype._addCurrentShapeDataToAnUpdate = function (update) {
            if (!update.selectedShape && this.selectedShapeConstructor) {
                update.selectedShape = (new this.selectedShapeConstructor()).getShapeName();
            }
            if (!update.selectedShapeOptions) {
                // Make a deep copy since we don't want some Shape implementation to change something by accident
                update.selectedShapeOptions = Object.assign(new CanvasWhiteboardShapeOptions(), this.generateShapePreviewOptions(), {
                    lineWidth: this.erasingEnabled ? this.eraserLineWidth : this.lineWidth
                });
            }
        };
        CanvasWhiteboardComponent.prototype.generateShapePreviewOptions = function () {
            return Object.assign(new CanvasWhiteboardShapeOptions(), {
                shouldFillShape: !!this.fillColor,
                fillStyle: this.fillColor,
                strokeStyle: this.strokeColor,
                lineWidth: this.lineWidth,
                lineJoin: this.lineJoin,
                lineCap: this.lineCap
            });
        };
        /**
         * Sends the update to all receiving ends as an Event emit. This is done as a batch operation (meaning
         * multiple updates are sent at the same time). If this method is called, after 100 ms all updates
         * that were made at that time will be packed up together and sent to the receiver.
         *
         * @param update The update object.
         * @return Emits an Array of Updates when the batch.
         */
        CanvasWhiteboardComponent.prototype._prepareUpdateForBatchDispatch = function (update) {
            var _this = this;
            this._batchUpdates.push(lodashEs.cloneDeep(update));
            if (!this._updateTimeout) {
                this._updateTimeout = setTimeout(function () {
                    _this.batchUpdate.emit(_this._batchUpdates);
                    _this._batchUpdates = [];
                    _this._updateTimeout = null;
                }, this.batchUpdateTimeoutDuration);
            }
        };
        /**
         * Draws an Array of Updates on the canvas.
         *
         * @param updates The array with Updates.
         */
        CanvasWhiteboardComponent.prototype.drawUpdates = function (updates) {
            var _this = this;
            if (this._canDraw) {
                this._drawMissingUpdates();
                updates.forEach(function (update) {
                    _this._draw(update);
                });
            }
            else {
                this._updatesNotDrawn = this._updatesNotDrawn.concat(updates);
            }
        };
        /**
         * Draw any missing updates that were received before the image was loaded
         */
        CanvasWhiteboardComponent.prototype._drawMissingUpdates = function () {
            var _this = this;
            if (this._updatesNotDrawn.length > 0) {
                var updatesToDraw = this._updatesNotDrawn;
                this._updatesNotDrawn = [];
                updatesToDraw.forEach(function (update) {
                    _this._draw(update);
                });
            }
        };
        // noinspection JSMethodCanBeStatic
        /**
         * Draws an image on the canvas
         *
         * @param context The context used to draw the image on the canvas.
         * @param image The image to draw.
         * @param x The X coordinate for the starting draw position.
         * @param y The Y coordinate for the starting draw position.
         * @param width The width of the image that will be drawn.
         * @param height The height of the image that will be drawn.
         * @param offsetX The offsetX if the image size is larger than the canvas (aspect Ratio)
         * @param offsetY The offsetY if the image size is larger than the canvas (aspect Ratio)
         */
        CanvasWhiteboardComponent.prototype._drawImage = function (context, image, x, y, width, height, offsetX, offsetY) {
            if (arguments.length === 2) {
                x = y = 0;
                width = context.canvas.width;
                height = context.canvas.height;
            }
            if (offsetX < 0)
                offsetX = 0;
            else if (offsetX > 1)
                offsetX = 1;
            if (offsetY < 0)
                offsetY = 0;
            else if (offsetY > 1)
                offsetY = 1;
            var imageWidth = image.width;
            var imageHeight = image.height;
            var radius = Math.min(width / imageWidth, height / imageHeight);
            var newWidth = imageWidth * radius;
            var newHeight = imageHeight * radius;
            var finalDrawX;
            var finalDrawY;
            var finalDrawWidth;
            var finalDrawHeight;
            var aspectRatio = 1;
            // decide which gap to fill
            if (newWidth < width) {
                aspectRatio = width / newWidth;
            }
            if (Math.abs(aspectRatio - 1) < 1e-14 && newHeight < height) {
                aspectRatio = height / newHeight;
            }
            newWidth *= aspectRatio;
            newHeight *= aspectRatio;
            // calculate source rectangle
            finalDrawWidth = imageWidth / (newWidth / width);
            finalDrawHeight = imageHeight / (newHeight / height);
            finalDrawX = (imageWidth - finalDrawWidth) * offsetX;
            finalDrawY = (imageHeight - finalDrawHeight) * offsetY;
            // make sure the source rectangle is valid
            if (finalDrawX < 0)
                finalDrawX = 0;
            if (finalDrawY < 0)
                finalDrawY = 0;
            if (finalDrawWidth > imageWidth)
                finalDrawWidth = imageWidth;
            if (finalDrawHeight > imageHeight)
                finalDrawHeight = imageHeight;
            // fill the image in destination rectangle
            context.drawImage(image, finalDrawX, finalDrawY, finalDrawWidth, finalDrawHeight, x, y, width, height);
        };
        /**
         * The HTMLCanvasElement.toDataURL() method returns a data URI containing a representation
         * of the image in the format specified by the type parameter (defaults to PNG).
         * The returned image is in a resolution of 96 dpi.
         * If the height or width of the canvas is 0, the string "data:," is returned.
         * If the requested type is not image/png, but the returned value starts with data:image/png, then the requested type is not supported.
         * Chrome also supports the image/webp type.
         *
         * @param returnedDataType A DOMString indicating the image format. The default format type is image/png.
         * @param returnedDataQuality A Number between 0 and 1 indicating image quality if the requested type
         * is image/jpeg or image/webp.
         * If this argument is anything else, the default value for image quality is used. The default value is 0.92.
         * Other arguments are ignored.
         */
        CanvasWhiteboardComponent.prototype.generateCanvasDataUrl = function (returnedDataType, returnedDataQuality) {
            if (returnedDataType === void 0) { returnedDataType = 'image/png'; }
            if (returnedDataQuality === void 0) { returnedDataQuality = 1; }
            return this.context.canvas.toDataURL(returnedDataType, returnedDataQuality);
        };
        /**
         * Generate a Blob object representing the content drawn on the canvas.
         * This file may be cached on the disk or stored in memory at the discretion of the user agent.
         * If type is not specified, the image type is image/png. The created image is in a resolution of 96dpi.
         * The third argument is used with image/jpeg images to specify the quality of the output.
         *
         * @param callbackFn The function that should be executed when the blob is created. Should accept a parameter Blob (for the result).
         * @param returnedDataType A DOMString indicating the image format. The default type is image/png.
         * @param returnedDataQuality A Number between 0 and 1 indicating image quality if the requested type is image/jpeg or image/webp.
         * If this argument is anything else, the default value for image quality is used. Other arguments are ignored.
         */
        CanvasWhiteboardComponent.prototype.generateCanvasBlob = function (callbackFn, returnedDataType, returnedDataQuality) {
            var _this = this;
            if (returnedDataType === void 0) { returnedDataType = 'image/png'; }
            if (returnedDataQuality === void 0) { returnedDataQuality = 1; }
            var toBlobMethod;
            if (typeof this.context.canvas.toBlob !== 'undefined') {
                toBlobMethod = this.context.canvas.toBlob.bind(this.context.canvas);
            }
            else if (typeof this.context.canvas.msToBlob !== 'undefined') {
                // For IE
                toBlobMethod = function (callback) {
                    if (callback)
                        callback(_this.context.canvas.msToBlob());
                };
            }
            if (toBlobMethod)
                toBlobMethod(function (blob) {
                    if (callbackFn && blob)
                        callbackFn(blob, returnedDataType);
                }, returnedDataType, returnedDataQuality);
        };
        /**
         * Generate a canvas image representation and download it locally
         * The name of the image is canvas_drawing_ + the current local Date and Time the image was created
         * Methods for standalone creation of the images in this method are left here for backwards compatibility
         *
         * @param returnedDataType A DOMString indicating the image format. The default type is image/png.
         * @param downloadData? The created string or Blob (IE).
         * @param customFileName? The name of the file that should be downloaded
         */
        CanvasWhiteboardComponent.prototype.downloadCanvasImage = function (returnedDataType, downloadData, customFileName) {
            if (returnedDataType === void 0) { returnedDataType = 'image/png'; }
            if (window.navigator.msSaveOrOpenBlob === undefined) {
                var downloadLink = document.createElement('a');
                downloadLink.setAttribute('href', downloadData ? downloadData : this.generateCanvasDataUrl(returnedDataType));
                var fileName = customFileName ? customFileName
                    : (this.downloadedFileName ? this.downloadedFileName : 'canvas_drawing_' + new Date().valueOf());
                downloadLink.setAttribute('download', fileName + this._generateDataTypeString(returnedDataType));
                document.body.appendChild(downloadLink);
                downloadLink.click();
                document.body.removeChild(downloadLink);
            }
            else {
                // IE-specific code
                if (downloadData) {
                    this._saveCanvasBlob(downloadData, returnedDataType);
                }
                else {
                    this.generateCanvasBlob(this._saveCanvasBlob.bind(this), returnedDataType);
                }
            }
        };
        /**
         * Save the canvas blob (IE) locally
         */
        CanvasWhiteboardComponent.prototype._saveCanvasBlob = function (blob, returnedDataType) {
            if (returnedDataType === void 0) { returnedDataType = 'image/png'; }
            window.navigator.msSaveOrOpenBlob(blob, 'canvas_drawing_' +
                new Date().valueOf() + this._generateDataTypeString(returnedDataType));
        };
        /**
         * This method generates a canvas url string or a canvas blob with the presented data type
         * A callback function is then invoked since the blob creation must be done via a callback
         *
         */
        CanvasWhiteboardComponent.prototype.generateCanvasData = function (callback, returnedDataType, returnedDataQuality) {
            if (returnedDataType === void 0) { returnedDataType = 'image/png'; }
            if (returnedDataQuality === void 0) { returnedDataQuality = 1; }
            if (window.navigator.msSaveOrOpenBlob === undefined) {
                if (callback)
                    callback(this.generateCanvasDataUrl(returnedDataType, returnedDataQuality));
            }
            else {
                this.generateCanvasBlob(callback, returnedDataType, returnedDataQuality);
            }
        };
        /**
         * Local method to invoke saving of the canvas data when clicked on the canvas Save button
         * This method will emit the generated data with the specified Event Emitter
         *
         */
        CanvasWhiteboardComponent.prototype.saveLocal = function (returnedDataType) {
            var _this = this;
            if (returnedDataType === void 0) { returnedDataType = 'image/png'; }
            this.generateCanvasData(function (generatedData) {
                _this.save.emit(generatedData);
                if (_this.shouldDownloadDrawing) {
                    _this.downloadCanvasImage(returnedDataType, generatedData);
                }
            });
        };
        CanvasWhiteboardComponent.prototype._generateDataTypeString = function (returnedDataType) {
            if (returnedDataType) {
                return '.' + returnedDataType.split('/')[1];
            }
            return '';
        };
        /**
         * Toggles the color picker window, delegating the showColorPicker Input to the ColorPickerComponent.
         * If no value is supplied (null/undefined) the current value will be negated and used.
         */
        CanvasWhiteboardComponent.prototype.toggleStrokeColorPicker = function (value) {
            this.showStrokeColorPicker = !this._isNullOrUndefined(value) ? value : !this.showStrokeColorPicker;
        };
        /**
         * Toggles the color picker window, delegating the showColorPicker Input to the ColorPickerComponent.
         * If no value is supplied (null/undefined) the current value will be negated and used.
         */
        CanvasWhiteboardComponent.prototype.toggleFillColorPicker = function (value) {
            this.showFillColorPicker = !this._isNullOrUndefined(value) ? value : !this.showFillColorPicker;
        };
        /**
         * Toggles the shape selector window, delegating the showShapeSelector Input to the CanvasWhiteboardShapeSelectorComponent.
         * If no value is supplied (null/undefined) the current value will be negated and used.
         */
        CanvasWhiteboardComponent.prototype.toggleShapeSelector = function (value) {
            this.showShapeSelector = !this._isNullOrUndefined(value) ? value : !this.showShapeSelector;
        };
        CanvasWhiteboardComponent.prototype.selectShape = function (newShapeBlueprint) {
            this.selectedShapeConstructor = newShapeBlueprint;
        };
        /**
         * Returns a deep copy of the current drawing history for the canvas.
         * The deep copy is returned because we don't want anyone to mutate the current history
         */
        CanvasWhiteboardComponent.prototype.getDrawingHistory = function () {
            return lodashEs.cloneDeep(this._updateHistory);
        };
        CanvasWhiteboardComponent.prototype._setErasing = function (value) {
            if (value) {
                // Draw invisible lines to imitate erasing
                this.context.globalCompositeOperation = 'destination-out';
                this.cachedStrokeColor = (' ' + this.strokeColor).slice(1);
                this.changeStrokeColor('rgba(255,255,255,1)');
                this.changeDetectorRef.detectChanges();
            }
            else {
                // Return to default values
                this.context.globalCompositeOperation = 'source-over';
                this.changeStrokeColor((' ' + this.cachedStrokeColor).slice(1));
                this.cachedStrokeColor = '';
                this.changeDetectorRef.detectChanges();
            }
        };
        /**
         * Unsubscribe from a given subscription if it is active
         */
        CanvasWhiteboardComponent.prototype._unsubscribe = function (subscription) {
            if (subscription) {
                subscription.unsubscribe();
            }
        };
        CanvasWhiteboardComponent.prototype._generateUUID = function () {
            return this._random4() + this._random4() + '-' + this._random4() + '-' + this._random4() + '-' +
                this._random4() + '-' + this._random4() + this._random4() + this._random4();
        };
        CanvasWhiteboardComponent.prototype._random4 = function () {
            return Math.floor((1 + Math.random()) * 0x10000)
                .toString(16)
                .substring(1);
        };
        return CanvasWhiteboardComponent;
    }());
    CanvasWhiteboardComponent.decorators = [
        { type: core.Component, args: [{
                    selector: 'drizm-whiteboard',
                    template: "<div class=\"canvas_wrapper_div\">\r\n  <div class=\"canvas_whiteboard_buttons\" *ngIf=\"!customWhiteboardUi; else customWhiteboardUi\">\r\n    <canvas-whiteboard-shape-selector\r\n      *ngIf=\"shapeSelectorEnabled\"\r\n      [showShapeSelector]=\"showShapeSelector\"\r\n      [selectedShapeConstructor]=\"selectedShapeConstructor\"\r\n      [shapeOptions]=\"generateShapePreviewOptions()\"\r\n      (toggleShapeSelector)=\"toggleShapeSelector($event)\"\r\n      (shapeSelected)=\"selectShape($event)\"></canvas-whiteboard-shape-selector>\r\n\r\n    <canvas-whiteboard-colorpicker\r\n      *ngIf=\"fillColorPickerEnabled\"\r\n      [previewText]=\"fillColorPickerText\"\r\n      [showColorPicker]=\"showFillColorPicker\"\r\n      [selectedColor]=\"fillColor\"\r\n      (toggleColorPicker)=\"toggleFillColorPicker($event)\"\r\n      (colorSelected)=\"changeFillColor($event)\">\r\n    </canvas-whiteboard-colorpicker>\r\n\r\n    <canvas-whiteboard-colorpicker\r\n      *ngIf=\"strokeColorPickerEnabled\"\r\n      [previewText]=\"strokeColorPickerText\"\r\n      [showColorPicker]=\"showStrokeColorPicker\"\r\n      [selectedColor]=\"cachedStrokeColor || strokeColor\"\r\n      (toggleColorPicker)=\"toggleStrokeColorPicker($event)\"\r\n      (colorSelected)=\"changeStrokeColor($event)\">\r\n    </canvas-whiteboard-colorpicker>\r\n\r\n\r\n    <button *ngIf=\"drawButtonEnabled\" (click)=\"toggleDrawingEnabled()\"\r\n            [class.canvas_whiteboard_button-draw_animated]=\"getDrawingEnabled()\"\r\n            class=\"canvas_whiteboard_button canvas_whiteboard_button-draw\" type=\"button\">\r\n      <i [class]=\"drawButtonClass\" aria-hidden=\"true\"></i> {{drawButtonText}}\r\n    </button>\r\n\r\n    <button *ngIf=\"clearButtonEnabled\" (click)=\"clearCanvasLocal()\" type=\"button\"\r\n            class=\"canvas_whiteboard_button canvas_whiteboard_button-clear\">\r\n      <i [class]=\"clearButtonClass\" aria-hidden=\"true\"></i> {{clearButtonText}}\r\n    </button>\r\n\r\n    <button *ngIf=\"undoButtonEnabled\" (click)=\"undoLocal()\" type=\"button\"\r\n            class=\"canvas_whiteboard_button canvas_whiteboard_button-undo\">\r\n      <i [class]=\"undoButtonClass\" aria-hidden=\"true\"></i> {{undoButtonText}}\r\n    </button>\r\n\r\n    <button *ngIf=\"redoButtonEnabled\" (click)=\"redoLocal()\" type=\"button\"\r\n            class=\"canvas_whiteboard_button canvas_whiteboard_button-redo\">\r\n      <i [class]=\"redoButtonClass\" aria-hidden=\"true\"></i> {{redoButtonText}}\r\n    </button>\r\n    <button *ngIf=\"saveDataButtonEnabled\" (click)=\"saveLocal()\" type=\"button\"\r\n            class=\"canvas_whiteboard_button canvas_whiteboard_button-save\">\r\n      <i [class]=\"saveDataButtonClass\" aria-hidden=\"true\"></i> {{saveDataButtonText}}\r\n    </button>\r\n\r\n    <button *ngIf=\"eraseButtonEnabled\" (click)=\"toggleErasingEnabled()\" type=\"button\"\r\n            class=\"canvas_whiteboard_button canvas_whiteboard_button-clear\"\r\n            [class.canvas_whiteboard_button-draw_animated]=\"erasingEnabled\">\r\n      <i [class]=\"eraseButtonClass\" aria-hidden=\"true\"></i> {{eraseButtonText}}\r\n    </button>\r\n  </div>\r\n\r\n  <canvas #canvas class=\"canvas_whiteboard\"></canvas>\r\n  <canvas\r\n    #incompleteShapesCanvas class=\"incomplete_shapes_canvas_whiteboard\"\r\n    (mousedown)=\"canvasUserEvents($event)\" (mouseup)=\"canvasUserEvents($event)\"\r\n    (mousemove)=\"canvasUserEvents($event)\" (mouseout)=\"canvasUserEvents($event)\"\r\n    (touchstart)=\"canvasUserEvents($event)\" (touchmove)=\"canvasUserEvents($event)\"\r\n    (touchend)=\"canvasUserEvents($event)\" (touchcancel)=\"canvasUserEvents($event)\">\r\n  </canvas>\r\n</div>\r\n",
                    viewProviders: [CanvasWhiteboardComponent],
                    styles: [".canvas_whiteboard_button{display:inline-block;outline:0;padding-top:7px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.canvas_whiteboard_buttons{z-index:3}@media (max-width:400px){.canvas_whiteboard_buttons{position:absolute;z-index:3;top:0;width:100%;text-align:center}}@media (min-width:401px){.canvas_whiteboard_buttons{position:absolute;right:0;color:#fff}}.canvas_whiteboard_buttons{padding:5px}.canvas_whiteboard_buttons>button{margin:5px}.canvas_whiteboard_button-draw_animated{-webkit-animation:pulsate 1s ease-out;-webkit-animation-iteration-count:infinite}@-webkit-keyframes pulsate{0%{-webkit-transform:scale(.1);opacity:0}50%{opacity:1}to{-webkit-transform:scale(1.2);opacity:0}}.canvas_wrapper_div{width:100%;height:100%}.canvas_whiteboard_button-clear{padding-top:5px}.canvas_whiteboard{position:absolute;z-index:1}.incomplete_shapes_canvas_whiteboard{position:absolute;z-index:2}"]
                },] }
    ];
    CanvasWhiteboardComponent.ctorParameters = function () { return [
        { type: core.NgZone },
        { type: core.ChangeDetectorRef },
        { type: CanvasWhiteboardService },
        { type: CanvasWhiteboardShapeService }
    ]; };
    CanvasWhiteboardComponent.propDecorators = {
        options: [{ type: core.Input }],
        batchUpdateTimeoutDuration: [{ type: core.Input }],
        imageUrl: [{ type: core.Input }],
        customWhiteboardUi: [{ type: core.Input }],
        aspectRatio: [{ type: core.Input }],
        drawButtonClass: [{ type: core.Input }],
        clearButtonClass: [{ type: core.Input }],
        eraseButtonClass: [{ type: core.Input }],
        undoButtonClass: [{ type: core.Input }],
        redoButtonClass: [{ type: core.Input }],
        saveDataButtonClass: [{ type: core.Input }],
        drawButtonText: [{ type: core.Input }],
        clearButtonText: [{ type: core.Input }],
        eraseButtonText: [{ type: core.Input }],
        undoButtonText: [{ type: core.Input }],
        redoButtonText: [{ type: core.Input }],
        saveDataButtonText: [{ type: core.Input }],
        strokeColorPickerText: [{ type: core.Input }],
        fillColorPickerText: [{ type: core.Input }],
        drawButtonEnabled: [{ type: core.Input }],
        clearButtonEnabled: [{ type: core.Input }],
        eraseButtonEnabled: [{ type: core.Input }],
        undoButtonEnabled: [{ type: core.Input }],
        redoButtonEnabled: [{ type: core.Input }],
        saveDataButtonEnabled: [{ type: core.Input }],
        shouldDownloadDrawing: [{ type: core.Input }],
        strokeColorPickerEnabled: [{ type: core.Input }],
        fillColorPickerEnabled: [{ type: core.Input }],
        lineWidth: [{ type: core.Input }],
        eraserLineWidth: [{ type: core.Input }],
        strokeColor: [{ type: core.Input }],
        startingColor: [{ type: core.Input }],
        scaleFactor: [{ type: core.Input }],
        drawingEnabled: [{ type: core.Input }],
        erasingEnabled: [{ type: core.Input }],
        showStrokeColorPicker: [{ type: core.Input }],
        showFillColorPicker: [{ type: core.Input }],
        downloadedFileName: [{ type: core.Input }],
        lineJoin: [{ type: core.Input }],
        lineCap: [{ type: core.Input }],
        shapeSelectorEnabled: [{ type: core.Input }],
        showShapeSelector: [{ type: core.Input }],
        fillColor: [{ type: core.Input }],
        clear: [{ type: core.Output }],
        undo: [{ type: core.Output }],
        redo: [{ type: core.Output }],
        batchUpdate: [{ type: core.Output }],
        imageLoaded: [{ type: core.Output }],
        save: [{ type: core.Output }],
        canvas: [{ type: core.ViewChild, args: ['canvas', { static: true },] }],
        _incompleteShapesCanvas: [{ type: core.ViewChild, args: ['incompleteShapesCanvas', { static: true },] }]
    };

    var CanvasWhiteboardShapeSelectorComponent = /** @class */ (function () {
        function CanvasWhiteboardShapeSelectorComponent(elementRef, canvasWhiteboardShapeService) {
            this.elementRef = elementRef;
            this.canvasWhiteboardShapeService = canvasWhiteboardShapeService;
            this.showShapeSelector = false;
            this.toggleShapeSelector = new core.EventEmitter();
            this.shapeSelected = new core.EventEmitter();
            this.registeredShapes$ = this.canvasWhiteboardShapeService.registeredShapes$;
        }
        CanvasWhiteboardShapeSelectorComponent.prototype.closeOnExternalClick = function (event) {
            if (!this.elementRef.nativeElement.contains(event.target) && this.showShapeSelector) {
                this.toggleShapeSelector.emit(false);
            }
        };
        CanvasWhiteboardShapeSelectorComponent.prototype.selectShape = function (shape) {
            this.shapeSelected.emit(shape);
            this.doToggleShapeSelector(null);
        };
        CanvasWhiteboardShapeSelectorComponent.prototype.doToggleShapeSelector = function (event) {
            if (event) {
                event.preventDefault();
            }
            this.toggleShapeSelector.emit(!this.showShapeSelector);
        };
        return CanvasWhiteboardShapeSelectorComponent;
    }());
    CanvasWhiteboardShapeSelectorComponent.decorators = [
        { type: core.Component, args: [{
                    selector: 'canvas-whiteboard-shape-selector',
                    template: "<div *ngIf=\"!showShapeSelector\" (click)=\"doToggleShapeSelector($event)\"\r\n     class=\"canvas-whiteboard-shape-selector-selected-preview\">\r\n  <canvas-whiteboard-shape-preview\r\n    [shapeConstructor]=\"selectedShapeConstructor\"\r\n    [shapeOptions]=\"shapeOptions\"></canvas-whiteboard-shape-preview>\r\n</div>\r\n<div class=\"canvas-whiteboard-shape-selector-wrapper\" *ngIf=\"showShapeSelector\">\r\n  <canvas-whiteboard-shape-preview\r\n    *ngFor=\"let shapeConstructor of registeredShapes$ | async\"\r\n    [shapeConstructor]=\"shapeConstructor\"\r\n    [shapeOptions]=\"shapeOptions\"\r\n    (click)=\"selectShape(shapeConstructor)\"></canvas-whiteboard-shape-preview>\r\n</div>\r\n",
                    styles: [".canvas-whiteboard-shape-selector-selected-preview{vertical-align:bottom;display:inline-block}.canvas-whiteboard-shape-selector-wrapper{display:block;padding:4px;border:1px solid #afafaf}"]
                },] }
    ];
    CanvasWhiteboardShapeSelectorComponent.ctorParameters = function () { return [
        { type: core.ElementRef },
        { type: CanvasWhiteboardShapeService }
    ]; };
    CanvasWhiteboardShapeSelectorComponent.propDecorators = {
        showShapeSelector: [{ type: core.Input }],
        selectedShapeConstructor: [{ type: core.Input }],
        shapeOptions: [{ type: core.Input }],
        toggleShapeSelector: [{ type: core.Output }],
        shapeSelected: [{ type: core.Output }],
        closeOnExternalClick: [{ type: core.HostListener, args: ['document:touchstart', ['$event'],] }, { type: core.HostListener, args: ['document:mousedown', ['$event'],] }]
    };

    var CanvasWhiteboardColorPickerComponent = /** @class */ (function () {
        function CanvasWhiteboardColorPickerComponent(elementRef) {
            this.elementRef = elementRef;
            this.selectedColor = 'rgba(0,0,0,1)';
            this.showColorPicker = false;
            this.toggleColorPicker = new core.EventEmitter();
            this.colorSelected = new core.EventEmitter();
            this.secondaryColorSelected = new core.EventEmitter();
        }
        CanvasWhiteboardColorPickerComponent.prototype.closeOnExternalClick = function (event) {
            if (!this.elementRef.nativeElement.contains(event.target) && this.showColorPicker) {
                this.toggleColorPicker.emit(false);
            }
        };
        /**
         * Initialize the canvas drawing context. If we have an aspect ratio set up, the canvas will resize
         * according to the aspect ratio.
         */
        CanvasWhiteboardColorPickerComponent.prototype.ngOnInit = function () {
            this._context = this.canvas.nativeElement.getContext('2d');
            this.createColorPalette();
        };
        CanvasWhiteboardColorPickerComponent.prototype.createColorPalette = function () {
            var gradient = this._context.createLinearGradient(0, 0, this._context.canvas.width, 0);
            gradient.addColorStop(0, 'rgb(255, 0, 0)');
            gradient.addColorStop(0.15, 'rgb(255, 0, 255)');
            gradient.addColorStop(0.33, 'rgb(0, 0, 255)');
            gradient.addColorStop(0.49, 'rgb(0, 255, 255)');
            gradient.addColorStop(0.67, 'rgb(0, 255, 0)');
            gradient.addColorStop(0.84, 'rgb(255, 255, 0)');
            gradient.addColorStop(1, 'rgb(255, 0, 0)');
            this._context.fillStyle = gradient;
            this._context.fillRect(0, 0, this._context.canvas.width, this._context.canvas.height);
            gradient = this._context.createLinearGradient(0, 0, 0, this._context.canvas.height);
            gradient.addColorStop(0, 'rgba(255, 255, 255, 1)');
            gradient.addColorStop(0.5, 'rgba(255, 255, 255, 0)');
            gradient.addColorStop(0.5, 'rgba(0, 0, 0, 0)');
            gradient.addColorStop(1, 'rgba(0, 0, 0, 1)');
            this._context.fillStyle = gradient;
            this._context.fillRect(0, 0, this._context.canvas.width, this._context.canvas.height);
        };
        CanvasWhiteboardColorPickerComponent.prototype.doToggleColorPicker = function (event) {
            if (event) {
                event.preventDefault();
            }
            this.toggleColorPicker.emit(!this.showColorPicker);
        };
        CanvasWhiteboardColorPickerComponent.prototype.determineColorFromCanvas = function (event) {
            var canvasRect = this._context.canvas.getBoundingClientRect();
            var imageData = this._context.getImageData(event.clientX - canvasRect.left, event.clientY - canvasRect.top, 1, 1);
            return "rgba(" + imageData.data[0] + ", " + imageData.data[1] + ", " + imageData.data[2] + ", " + imageData.data[3] + ")";
        };
        CanvasWhiteboardColorPickerComponent.prototype.selectColor = function (color) {
            this.colorSelected.emit(color);
            this.doToggleColorPicker(null);
        };
        return CanvasWhiteboardColorPickerComponent;
    }());
    CanvasWhiteboardColorPickerComponent.decorators = [
        { type: core.Component, args: [{
                    selector: 'canvas-whiteboard-colorpicker',
                    template: "<div [hidden]=\"showColorPicker\" class=\"canvas-whiteboard-colorpicker-input\"\r\n     (click)=\"doToggleColorPicker($event)\">\r\n  <div class=\"selected-color-type-wrapper\">{{previewText}}</div>\r\n  <div class=\"selected-color-preview\" [style.background]=\"selectedColor\"></div>\r\n</div>\r\n<div [hidden]=\"!showColorPicker\" class=\"canvas-whiteboard-colorpicker-wrapper\">\r\n  <div (click)=\"selectColor('transparent')\" class=\"transparent-color\">Transparent</div>\r\n  <canvas #canvaswhiteboardcolorpicker class=\"canvas-whiteboard-colorpicker\" width=\"284\" height=\"155\"\r\n          (click)=\"selectColor(determineColorFromCanvas($event))\"></canvas>\r\n</div>\r\n",
                    styles: [".selected-color-preview{width:100%;height:20%;position:absolute;bottom:0;left:0}.selected-color-type-wrapper{display:inline-block;height:100%;width:100%;text-align:center;font-size:14px;color:#000}.transparent-color{font-size:14px}.canvas-whiteboard-colorpicker-wrapper{border:1px solid #afafaf;color:#000}@media (min-width:401px){.canvas-whiteboard-colorpicker-wrapper{position:absolute}}.canvas-whiteboard-colorpicker-input{display:inline-block;position:relative;width:44px;height:44px;margin:5px;cursor:pointer;color:#000}"]
                },] }
    ];
    CanvasWhiteboardColorPickerComponent.ctorParameters = function () { return [
        { type: core.ElementRef }
    ]; };
    CanvasWhiteboardColorPickerComponent.propDecorators = {
        previewText: [{ type: core.Input }],
        selectedColor: [{ type: core.Input }],
        canvas: [{ type: core.ViewChild, args: ['canvaswhiteboardcolorpicker', { static: true },] }],
        showColorPicker: [{ type: core.Input }],
        toggleColorPicker: [{ type: core.Output }],
        colorSelected: [{ type: core.Output }],
        secondaryColorSelected: [{ type: core.Output }],
        closeOnExternalClick: [{ type: core.HostListener, args: ['document:touchstart', ['$event'],] }, { type: core.HostListener, args: ['document:mousedown', ['$event'],] }]
    };

    var CanvasWhiteboardShapePreviewComponent = /** @class */ (function () {
        function CanvasWhiteboardShapePreviewComponent() {
        }
        CanvasWhiteboardShapePreviewComponent.prototype.ngAfterViewInit = function () {
            this.drawShapePreview();
        };
        CanvasWhiteboardShapePreviewComponent.prototype.ngOnChanges = function (changes) {
            if (changes.shapeConstructor || changes.shapeOptions) {
                this.drawShapePreview();
            }
        };
        CanvasWhiteboardShapePreviewComponent.prototype.drawShapePreview = function () {
            if (!this.canvas) {
                return;
            }
            var context = this.canvas.nativeElement.getContext('2d');
            context.clearRect(0, 0, context.canvas.width, context.canvas.height);
            var concreteShape = new this.shapeConstructor(new CanvasWhiteboardPoint(0, 0), Object.assign(new CanvasWhiteboardShapeOptions(), this.shapeOptions));
            concreteShape.drawPreview(context);
        };
        return CanvasWhiteboardShapePreviewComponent;
    }());
    CanvasWhiteboardShapePreviewComponent.decorators = [
        { type: core.Component, args: [{
                    selector: 'canvas-whiteboard-shape-preview',
                    template: "<canvas #canvasWhiteboardShapePreview width=\"50px\" height=\"50px\"\r\n        class=\"canvas-whiteboard-shape-preview-canvas\"></canvas>\r\n",
                    styles: [".canvas-whiteboard-shape-preview-canvas{cursor:pointer}"]
                },] }
    ];
    CanvasWhiteboardShapePreviewComponent.propDecorators = {
        shapeConstructor: [{ type: core.Input }],
        shapeOptions: [{ type: core.Input }],
        canvas: [{ type: core.ViewChild, args: ['canvasWhiteboardShapePreview',] }]
    };

    var CanvasWhiteboardModule = /** @class */ (function () {
        function CanvasWhiteboardModule() {
        }
        return CanvasWhiteboardModule;
    }());
    CanvasWhiteboardModule.decorators = [
        { type: core.NgModule, args: [{
                    declarations: [
                        CanvasWhiteboardComponent,
                        CanvasWhiteboardColorPickerComponent,
                        CanvasWhiteboardShapeSelectorComponent,
                        CanvasWhiteboardShapePreviewComponent
                    ],
                    imports: [
                        common.CommonModule
                    ],
                    providers: [
                        CanvasWhiteboardService,
                        CanvasWhiteboardShapeService
                    ],
                    exports: [CanvasWhiteboardComponent]
                },] }
    ];

    /*
     * Public API Surface of @drizm/ng-whiteboard
     */

    /**
     * Generated bundle index. Do not edit.
     */

    exports.CanvasWhiteboardColorPickerComponent = CanvasWhiteboardColorPickerComponent;
    exports.CanvasWhiteboardComponent = CanvasWhiteboardComponent;
    exports.CanvasWhiteboardModule = CanvasWhiteboardModule;
    exports.CanvasWhiteboardPoint = CanvasWhiteboardPoint;
    exports.CanvasWhiteboardService = CanvasWhiteboardService;
    exports.CanvasWhiteboardShape = CanvasWhiteboardShape;
    exports.CanvasWhiteboardShapeOptions = CanvasWhiteboardShapeOptions;
    exports.CanvasWhiteboardShapePreviewComponent = CanvasWhiteboardShapePreviewComponent;
    exports.CanvasWhiteboardShapeSelectorComponent = CanvasWhiteboardShapeSelectorComponent;
    exports.CanvasWhiteboardShapeService = CanvasWhiteboardShapeService;
    exports.CanvasWhiteboardUpdate = CanvasWhiteboardUpdate;
    exports.CircleShape = CircleShape;
    exports.FreeHandShape = FreeHandShape;
    exports.LineShape = LineShape;
    exports.RectangleShape = RectangleShape;
    exports.SmileyShape = SmileyShape;
    exports.StarShape = StarShape;

    Object.defineProperty(exports, '__esModule', { value: true });

})));
//# sourceMappingURL=drizm-ng-whiteboard.umd.js.map