UNPKG

ngx-image-drawing

Version:
562 lines (556 loc) 25.6 kB
import { __spread, __decorate, __metadata } from 'tslib'; import { CommonModule } from '@angular/common'; import { EventEmitter, Input, TemplateRef, Output, Component, NgModule } from '@angular/core'; import { fabric } from 'fabric'; import { Observable, of } from 'rxjs'; import { switchMap } from 'rxjs/operators'; var I18nFr = { saveBtn: 'Enregistrer', cancelBtn: 'Annuler', loadImage: 'Charger une image depuis votre PC', loadImageUrl: 'Charger une image depuis une URL', loading: 'Chargement', loadError: 'Erreur de chargement %@', removeImage: 'Supprimer l\'image', sizes: { small: 'Petit', medium: 'Moyen', large: 'Gros' }, undo: 'Annuler', redo: 'Répter', clear: 'Effacer', colors: { black: 'Noir', white: 'Blanc', yellow: 'Jaune', red: 'Rouge', green: 'Vert', blue: 'Blue', purple: 'Violet' }, tools: { brush: 'Pinceau' } }; var I18nEn = { saveBtn: 'Save', cancelBtn: 'Cancel', loadImage: 'Load image', loadImageUrl: 'Load image from URL', loading: 'Loading', loadError: 'Error loading %@', removeImage: 'Remove image', sizes: { small: 'Small', medium: 'Medium', large: 'Large' }, undo: 'Undo', redo: 'Redo', clear: 'Clear', colors: { black: 'Black', white: 'White', yellow: 'Yellow', red: 'Red', green: 'Green', blue: 'Blue', purple: 'Purple', }, tools: { brush: 'Brush' } }; var i18nLanguages = { fr: I18nFr, en: I18nEn }; var ImageDrawingComponent = /** @class */ (function () { function ImageDrawingComponent() { this.forceSizeCanvas = true; this.forceSizeExport = false; this.enableRemoveImage = false; this.enableLoadAnotherImage = false; this.enableTooltip = true; this.showCancelButton = true; this.locale = 'en'; /* @deprecated Use i18n.saveBtn */ this.saveBtnText = 'Save'; /* @deprecated Use i18n.cancelBtn */ this.cancelBtnText = 'Cancel'; /* @deprecated Use i18n.loading */ this.loadingText = 'Loading…'; /* @deprecated Use i18n.loadError */ this.errorText = 'Error loading %@'; this.outputMimeType = 'image/jpeg'; this.outputQuality = 0.8; this.borderCss = 'none'; this.drawingSizes = { small: 5, medium: 10, large: 25, }; this.colors = { black: '#000', white: '#fff', yellow: '#ffeb3b', red: '#f44336', blue: '#2196f3', green: '#4caf50', purple: '#7a08af', }; this.save = new EventEmitter(); this.cancel = new EventEmitter(); this.currentTool = 'brush'; this.currentSize = 'medium'; this.currentColor = 'black'; this.i18n = I18nEn; this.canUndo = false; this.canRedo = false; this.isLoading = false; this.hasError = false; this.errorMessage = ''; this.stack = []; this.colorsName = []; this.drawingSizesName = []; } ImageDrawingComponent.prototype.ngOnInit = function () { var _this = this; this.colorsName = Object.keys(this.colors); this.drawingSizesName = Object.keys(this.drawingSizes); this.canvas = new fabric.Canvas('canvas', { hoverCursor: 'pointer', isDrawingMode: true, }); this.canvas.backgroundColor = 'white'; if (this.src) { this.importPhotoFromSrc(this.src); } else { if (!this.width || !this.height) { throw new Error('No width or hight given !'); } this.canvas.setWidth(this.width); this.canvas.setHeight(this.height); } this.canvas.on('path:created', function () { _this.stack = []; _this.setUndoRedo(); }); this.selectTool(this.currentTool); this.selectColor(this.currentColor); this.selectDrawingSize(this.currentSize); if (this.locale && i18nLanguages[this.locale.toLowerCase()]) { this.i18n = i18nLanguages[this.locale.toLowerCase()]; } // FIXME remove after a while because properties are now deprecated if (this.saveBtnText) { this.i18n.saveBtn = this.saveBtnText; } if (this.cancelBtnText) { this.i18n.cancelBtn = this.cancelBtnText; } if (this.loadingText) { this.i18n.loading = this.loadingText; } if (this.errorText) { this.i18n.loadError = this.errorText; } }; // Tools ImageDrawingComponent.prototype.selectTool = function (tool) { this.currentTool = tool; }; ImageDrawingComponent.prototype.selectDrawingSize = function (size) { this.currentSize = size; if (this.canvas) { this.canvas.freeDrawingBrush.width = this.drawingSizes[size]; } }; ImageDrawingComponent.prototype.selectColor = function (color) { this.currentColor = color; if (this.canvas) { this.canvas.freeDrawingBrush.color = this.colors[color]; } }; // Actions ImageDrawingComponent.prototype.undo = function () { if (this.canUndo) { var lastId = this.canvas.getObjects().length - 1; var lastObj = this.canvas.getObjects()[lastId]; this.stack.push(lastObj); this.canvas.remove(lastObj); this.setUndoRedo(); } }; ImageDrawingComponent.prototype.redo = function () { if (this.canRedo) { var firstInStack = this.stack.splice(-1, 1)[0]; if (firstInStack) { this.canvas.insertAt(firstInStack, this.canvas.getObjects().length - 1, false); } this.setUndoRedo(); } }; ImageDrawingComponent.prototype.clearCanvas = function () { var _a; if (this.canvas) { (_a = this.canvas).remove.apply(_a, __spread(this.canvas.getObjects())); this.setUndoRedo(); } }; ImageDrawingComponent.prototype.saveImage = function () { var _this = this; if (!this.forceSizeExport || (this.forceSizeExport && this.width && this.height)) { var canvasScaledElement = document.createElement('canvas'); var canvasScaled_1 = new fabric.Canvas(canvasScaledElement); canvasScaled_1.backgroundColor = 'white'; new Observable(function (observer) { if (_this.imageUsed) { if (_this.forceSizeExport) { canvasScaled_1.setWidth(_this.width); canvasScaled_1.setHeight(_this.height); _this.imageUsed.cloneAsImage(function (imageCloned) { imageCloned.scaleToWidth(_this.width, false); imageCloned.scaleToHeight(_this.height, false); canvasScaled_1.setBackgroundImage(imageCloned, function (img) { if (!img) { observer.error(new Error('Impossible to draw the image on the temporary canvas')); } observer.next(canvasScaled_1); observer.complete(); }, { crossOrigin: 'anonymous', originX: 'left', originY: 'top' }); }); } else { canvasScaled_1.setBackgroundImage(_this.imageUsed, function (img) { if (!img) { observer.error(new Error('Impossible to draw the image on the temporary canvas')); } canvasScaled_1.setWidth(img.width); canvasScaled_1.setHeight(img.height); observer.next(canvasScaled_1); observer.complete(); }, { crossOrigin: 'anonymous', originX: 'left', originY: 'top' }); } } else { canvasScaled_1.setWidth(_this.width); canvasScaled_1.setHeight(_this.height); } }).pipe(switchMap(function () { var process = of(canvasScaled_1); if (_this.canvas.getObjects().length > 0) { var ratioX_1 = canvasScaled_1.getWidth() / _this.canvas.getWidth(); var ratioY_1 = canvasScaled_1.getHeight() / _this.canvas.getHeight(); _this.canvas.getObjects().forEach(function (originalObject, i) { process = process.pipe(switchMap(function () { return new Observable(function (observerObject) { originalObject.clone(function (clonedObject) { clonedObject.set('left', originalObject.left * ratioX_1); clonedObject.set('top', originalObject.top * ratioY_1); clonedObject.scaleToWidth(originalObject.width * ratioX_1); clonedObject.scaleToHeight(originalObject.height * ratioY_1); canvasScaled_1.insertAt(clonedObject, i, false); canvasScaled_1.renderAll(); observerObject.next(canvasScaled_1); observerObject.complete(); }); }); })); }); } return process; })).subscribe(function () { canvasScaled_1.renderAll(); canvasScaled_1.getElement().toBlob(function (data) { _this.save.emit(data); }, _this.outputMimeType, _this.outputQuality); }); } else { this.canvas.getElement().toBlob(function (data) { _this.save.emit(data); }, this.outputMimeType, this.outputQuality); } }; ImageDrawingComponent.prototype.cancelAction = function () { this.cancel.emit(); }; ImageDrawingComponent.prototype.getTextTranslated = function (name) { var strOk = name.split('.').reduce(function (o, i) { return o[i]; }, this.i18n); if (this.i18nUser) { try { var str = name.split('.').reduce(function (o, i) { return o[i]; }, this.i18nUser); if (str) { strOk = str; } } catch (e) { // if we pass here, ignored } } if (!strOk) { console.error(name + ' translation not found !'); } return strOk; }; ImageDrawingComponent.prototype.getTooltipTranslated = function (name) { if (this.enableTooltip) { return this.getTextTranslated(name); } else { return ''; } }; ImageDrawingComponent.prototype.setUndoRedo = function () { this.canUndo = this.canvas.getObjects().length > 0; this.canRedo = this.stack.length > 0; this.canvas.renderAll(); }; ImageDrawingComponent.prototype.importPhotoFromFile = function (event) { if (event.target.files && event.target.files.length > 0) { var file = event.target.files[0]; if (file.type.match('image.*')) { this.importPhotoFromBlob(file); } else { throw new Error('Not an image !'); } } }; ImageDrawingComponent.prototype.removeImage = function () { if (this.imageUsed) { this.imageUsed.dispose(); this.imageUsed = null; } this.canvas.backgroundImage = null; if (this.width && this.height) { this.canvas.setWidth(this.width); this.canvas.setHeight(this.height); } this.canvas.renderAll(); }; Object.defineProperty(ImageDrawingComponent.prototype, "hasImage", { get: function () { return !!this.canvas.backgroundImage; }, enumerable: true, configurable: true }); ImageDrawingComponent.prototype.importPhotoFromSrc = function (src) { var _this = this; this.isLoading = true; var isFirstTry = true; var imgEl = new Image(); imgEl.setAttribute('crossOrigin', 'anonymous'); imgEl.src = src; imgEl.onerror = function () { // Retry with cors proxy if (isFirstTry) { imgEl.src = 'https://cors-anywhere.herokuapp.com/' + _this.src; isFirstTry = false; } else { _this.isLoading = false; _this.hasError = true; _this.errorMessage = _this.getTextTranslated('loadError').replace('%@', _this.src); } }; imgEl.onload = function () { _this.isLoading = false; _this.imageUsed = new fabric.Image(imgEl); _this.imageUsed.cloneAsImage(function (image) { var width = imgEl.width; var height = imgEl.height; if (_this.width) { width = _this.width; } if (_this.height) { height = _this.height; } image.scaleToWidth(width, false); image.scaleToHeight(height, false); _this.canvas.setBackgroundImage(image, (function (img) { if (img) { if (_this.forceSizeCanvas) { _this.canvas.setWidth(width); _this.canvas.setHeight(height); } else { _this.canvas.setWidth(image.getScaledWidth()); _this.canvas.setHeight(image.getScaledHeight()); } } }), { crossOrigin: 'anonymous', originX: 'left', originY: 'top' }); }); }; }; ImageDrawingComponent.prototype.importPhotoFromBlob = function (file) { var _this = this; var reader = new FileReader(); reader.readAsDataURL(file); reader.onload = function (evtReader) { if (evtReader.target.readyState == FileReader.DONE) { _this.importPhotoFromSrc(evtReader.target.result); } }; }; ImageDrawingComponent.prototype.importPhotoFromUrl = function () { var url = prompt(this.getTooltipTranslated('loadImageUrl')); if (url) { this.importPhotoFromSrc(url); } }; ImageDrawingComponent.prototype.ngOnChanges = function (changes) { if (changes.src && !changes.src.firstChange && changes.src.currentValue) { if (typeof changes.src.currentValue === 'string') { this.importPhotoFromSrc(changes.src.currentValue); } else if (changes.src.currentValue instanceof Blob) { this.importPhotoFromBlob(changes.src.currentValue); } } }; __decorate([ Input(), __metadata("design:type", String) ], ImageDrawingComponent.prototype, "src", void 0); __decorate([ Input(), __metadata("design:type", Number) ], ImageDrawingComponent.prototype, "width", void 0); __decorate([ Input(), __metadata("design:type", Number) ], ImageDrawingComponent.prototype, "height", void 0); __decorate([ Input(), __metadata("design:type", Object) ], ImageDrawingComponent.prototype, "forceSizeCanvas", void 0); __decorate([ Input(), __metadata("design:type", Object) ], ImageDrawingComponent.prototype, "forceSizeExport", void 0); __decorate([ Input(), __metadata("design:type", Object) ], ImageDrawingComponent.prototype, "enableRemoveImage", void 0); __decorate([ Input(), __metadata("design:type", Object) ], ImageDrawingComponent.prototype, "enableLoadAnotherImage", void 0); __decorate([ Input(), __metadata("design:type", Object) ], ImageDrawingComponent.prototype, "enableTooltip", void 0); __decorate([ Input(), __metadata("design:type", Object) ], ImageDrawingComponent.prototype, "showCancelButton", void 0); __decorate([ Input('i18n'), __metadata("design:type", Object) ], ImageDrawingComponent.prototype, "i18nUser", void 0); __decorate([ Input(), __metadata("design:type", String) ], ImageDrawingComponent.prototype, "locale", void 0); __decorate([ Input(), __metadata("design:type", Object) ], ImageDrawingComponent.prototype, "saveBtnText", void 0); __decorate([ Input(), __metadata("design:type", Object) ], ImageDrawingComponent.prototype, "cancelBtnText", void 0); __decorate([ Input(), __metadata("design:type", Object) ], ImageDrawingComponent.prototype, "loadingText", void 0); __decorate([ Input(), __metadata("design:type", Object) ], ImageDrawingComponent.prototype, "errorText", void 0); __decorate([ Input(), __metadata("design:type", TemplateRef) ], ImageDrawingComponent.prototype, "loadingTemplate", void 0); __decorate([ Input(), __metadata("design:type", TemplateRef) ], ImageDrawingComponent.prototype, "errorTemplate", void 0); __decorate([ Input(), __metadata("design:type", Object) ], ImageDrawingComponent.prototype, "outputMimeType", void 0); __decorate([ Input(), __metadata("design:type", Object) ], ImageDrawingComponent.prototype, "outputQuality", void 0); __decorate([ Input(), __metadata("design:type", String) ], ImageDrawingComponent.prototype, "borderCss", void 0); __decorate([ Input(), __metadata("design:type", Object) ], ImageDrawingComponent.prototype, "drawingSizes", void 0); __decorate([ Input(), __metadata("design:type", Object) ], ImageDrawingComponent.prototype, "colors", void 0); __decorate([ Output(), __metadata("design:type", EventEmitter) ], ImageDrawingComponent.prototype, "save", void 0); __decorate([ Output(), __metadata("design:type", EventEmitter) ], ImageDrawingComponent.prototype, "cancel", void 0); ImageDrawingComponent = __decorate([ Component({ selector: 'image-drawing', template: "<link href=\"https://fonts.googleapis.com/icon?family=Material+Icons\" rel=\"stylesheet\" />\n\n<div class=\"loading\" *ngIf=\"isLoading\">\n <ng-container *ngTemplateOutlet=\"loadingTemplate ? loadingTemplate : defaultLoading\"></ng-container>\n</div>\n<div class=\"error\" *ngIf=\"hasError\">\n <ng-container *ngTemplateOutlet=\"errorTemplate ? errorTemplate : defaultError\"></ng-container>\n</div>\n\n<ng-template #defaultLoading><p>{{ getTextTranslated('loading') }}</p></ng-template>\n<ng-template #defaultError> <p>{{ errorMessage }}</p> </ng-template>\n\n<div [ngStyle]=\"{ border: borderCss }\">\n <canvas id=\"canvas\"></canvas>\n</div>\n<div class=\"toolbar\" *ngIf=\"!isLoading\">\n <div class=\"tools\">\n <div class=\"row\">\n <i class=\"material-icons btn\" [class.selected]=\"currentTool == 'brush'\" (click)=\"selectTool('brush')\"\n [title]=\"getTooltipTranslated('tools.brush')\">brush</i>\n <span *ngFor=\"let drawingSizeName of drawingSizesName\" class=\"size btn\"\n [style.width.px]=\"drawingSizes[drawingSizeName] * 0.8 + 8\"\n [style.height.px]=\"drawingSizes[drawingSizeName] * 0.8 + 8\"\n [style.borderRadius.px]=\"drawingSizes[drawingSizeName] * 0.4 + 4\"\n [class.selected]=\"currentSize == drawingSizeName\"\n [title]=\"getTooltipTranslated('sizes.' + drawingSizeName)\"\n (click)=\"selectDrawingSize(drawingSizeName)\">\n </span>\n\n <input style=\"display: none\" type=\"file\" #fileInput (change)=\"importPhotoFromFile($event)\"\n accept=\"image/*\"/>\n <i class=\"material-icons btn\" *ngIf=\"enableLoadAnotherImage && !hasImage\" (click)=\"fileInput.click();\"\n [title]=\"getTooltipTranslated('loadImage')\">attach_file</i>\n <i class=\"material-icons btn\" *ngIf=\"enableLoadAnotherImage && !hasImage\" (click)=\"importPhotoFromUrl()\"\n [title]=\"getTooltipTranslated('loadImageUrl')\">insert_drive_file</i>\n <i class=\"material-icons btn\" *ngIf=\"enableRemoveImage && hasImage\" (click)=\"removeImage()\"\n [title]=\"getTooltipTranslated('removeImage')\">clear</i>\n\n <i class=\"material-icons btn\" [class.disabled]=\"!canUndo\" (click)=\"undo()\"\n [title]=\"getTooltipTranslated('undo')\">undo</i>\n <i class=\"material-icons btn\" [class.disabled]=\"!canRedo\" (click)=\"redo()\"\n [title]=\"getTooltipTranslated('redo')\">redo</i>\n <i class=\"material-icons btn\" (click)=\"clearCanvas()\" [title]=\"getTooltipTranslated('clear')\">delete</i>\n </div>\n <div class=\"row\">\n <div *ngFor=\"let colorName of colorsName\" [class.selected]=\"currentColor == colorName\" class=\"color\"\n [ngClass]=\"colorName\"\n [style.background]=\"colors[colorName]\" [title]=\"getTooltipTranslated('colors.' + colorName)\"\n (click)=\"selectColor(colorName)\">\n </div>\n </div>\n </div>\n <div class=\"buttons\">\n <a href=\"#\" class=\"button btn-primary\"\n (click)=\"saveImage(); $event.preventDefault()\">{{ getTextTranslated('saveBtn') }}</a>\n <a href=\"#\" class=\"button btn-light\" *ngIf=\"showCancelButton\"\n (click)=\"cancelAction(); $event.preventDefault()\">{{ getTextTranslated('cancelBtn') }}</a>\n </div>\n <!-- Any additional toolbar buttons to be projected by the consuming app -->\n <ng-content></ng-content>\n</div>\n", styles: [":host{display:flex;flex-direction:column;align-items:center}:host .toolbar{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap}:host .tools{display:inline-flex;flex-direction:column;padding:20px;margin:10px;background:#fff;border-radius:6px;box-shadow:0 3px 10px rgba(0,0,0,.4)}:host .row{display:flex;width:300px;justify-content:space-around;align-items:center}:host .row:first-child{margin-bottom:10px}:host .btn{cursor:pointer}:host .btn.selected{color:#bdbdbd}:host .btn.disabled{cursor:initial;color:#bdbdbd}:host .size{background-color:#000}:host .size.selected{background-color:#bdbdbd}:host .color{width:28px;height:28px;border-radius:14px;cursor:pointer;display:flex;align-items:center;justify-content:center}:host .color.selected::after{content:\"\";width:10px;height:10px;background:#000;display:flex;border-radius:5px}:host .color.black{background-color:#000}:host .color.black.selected::after{background:#fff}:host .color.white{border:1px solid #a7a7a7}:host .buttons{margin:10px;display:flex;flex-direction:column}:host .button{cursor:pointer;outline:0;border:none;white-space:nowrap;text-decoration:none;vertical-align:baseline;text-align:center;min-width:64px;line-height:36px;padding:3px 16px;border-radius:4px;overflow:visible;transition:background .4s cubic-bezier(.25,.8,.25,1),box-shadow 280ms cubic-bezier(.4,0,.2,1);margin:10px}:host .button:hover{text-decoration:none!important}:host .button.btn-primary{background-color:#ef5f27;color:#fff}:host .button.btn-primary:hover{background-color:rgba(239,95,39,.8)}:host .button.btn-light{color:#ef5f27}:host .button.btn-light:hover{background-color:rgba(239,95,39,.1)}"] }), __metadata("design:paramtypes", []) ], ImageDrawingComponent); return ImageDrawingComponent; }()); var ImageDrawingModule = /** @class */ (function () { function ImageDrawingModule() { } ImageDrawingModule = __decorate([ NgModule({ declarations: [ ImageDrawingComponent ], exports: [ ImageDrawingComponent, ], imports: [ CommonModule ] }) ], ImageDrawingModule); return ImageDrawingModule; }()); /** * Generated bundle index. Do not edit. */ export { ImageDrawingComponent, ImageDrawingModule }; //# sourceMappingURL=ngx-image-drawing.js.map