Spaces:
Running
Running
| import extend from 'tui-code-snippet/object/extend'; | |
| import Imagetracer from '@/helper/imagetracer'; | |
| import { isSupportFileApi, base64ToBlob, toInteger, isEmptyCropzone, includes } from '@/util'; | |
| import { eventNames, historyNames, drawingModes, drawingMenuNames, zoomModes } from '@/consts'; | |
| export default { | |
| /** | |
| * Get ui actions | |
| * @returns {Object} actions for ui | |
| * @private | |
| */ | |
| getActions() { | |
| return { | |
| main: this._mainAction(), | |
| shape: this._shapeAction(), | |
| crop: this._cropAction(), | |
| resize: this._resizeAction(), | |
| flip: this._flipAction(), | |
| rotate: this._rotateAction(), | |
| text: this._textAction(), | |
| mask: this._maskAction(), | |
| draw: this._drawAction(), | |
| icon: this._iconAction(), | |
| filter: this._filterAction(), | |
| history: this._historyAction(), | |
| }; | |
| }, | |
| /** | |
| * Main Action | |
| * @returns {Object} actions for ui main | |
| * @private | |
| */ | |
| _mainAction() { | |
| const exitCropOnAction = () => { | |
| if (this.ui.submenu === 'crop') { | |
| this.stopDrawingMode(); | |
| this.ui.changeMenu('crop'); | |
| } | |
| }; | |
| const setAngleRangeBarOnAction = (angle) => { | |
| if (this.ui.submenu === 'rotate') { | |
| this.ui.rotate.setRangeBarAngle('setAngle', angle); | |
| } | |
| }; | |
| const setFilterStateRangeBarOnAction = (filterOptions) => { | |
| if (this.ui.submenu === 'filter') { | |
| this.ui.filter.setFilterState(filterOptions); | |
| } | |
| }; | |
| const onEndUndoRedo = (result) => { | |
| setAngleRangeBarOnAction(result); | |
| setFilterStateRangeBarOnAction(result); | |
| return result; | |
| }; | |
| const toggleZoomMode = () => { | |
| const zoomMode = this._graphics.getZoomMode(); | |
| this.stopDrawingMode(); | |
| if (zoomMode !== zoomModes.ZOOM) { | |
| this.startDrawingMode(drawingModes.ZOOM); | |
| this._graphics.startZoomInMode(); | |
| } else { | |
| this._graphics.endZoomInMode(); | |
| } | |
| }; | |
| const toggleHandMode = () => { | |
| const zoomMode = this._graphics.getZoomMode(); | |
| this.stopDrawingMode(); | |
| if (zoomMode !== zoomModes.HAND) { | |
| this.startDrawingMode(drawingModes.ZOOM); | |
| this._graphics.startHandMode(); | |
| } else { | |
| this._graphics.endHandMode(); | |
| } | |
| }; | |
| const initFilterState = () => { | |
| if (this.ui.filter) { | |
| this.ui.filter.initFilterCheckBoxState(); | |
| } | |
| }; | |
| return extend( | |
| { | |
| initLoadImage: (imagePath, imageName) => | |
| this.loadImageFromURL(imagePath, imageName).then((sizeValue) => { | |
| exitCropOnAction(); | |
| this.ui.initializeImgUrl = imagePath; | |
| this.ui.resizeEditor({ imageSize: sizeValue }); | |
| this.clearUndoStack(); | |
| this._invoker.fire(eventNames.EXECUTE_COMMAND, historyNames.LOAD_IMAGE); | |
| }), | |
| undo: () => { | |
| if (!this.isEmptyUndoStack()) { | |
| exitCropOnAction(); | |
| this.deactivateAll(); | |
| this.undo().then(onEndUndoRedo); | |
| } | |
| }, | |
| redo: () => { | |
| if (!this.isEmptyRedoStack()) { | |
| exitCropOnAction(); | |
| this.deactivateAll(); | |
| this.redo().then(onEndUndoRedo); | |
| } | |
| }, | |
| reset: () => { | |
| exitCropOnAction(); | |
| this.loadImageFromURL(this.ui.initializeImgUrl, 'resetImage').then((sizeValue) => { | |
| exitCropOnAction(); | |
| initFilterState(); | |
| this.ui.resizeEditor({ imageSize: sizeValue }); | |
| this.clearUndoStack(); | |
| this._initHistory(); | |
| }); | |
| }, | |
| delete: () => { | |
| this.ui.changeHelpButtonEnabled('delete', false); | |
| exitCropOnAction(); | |
| this.removeActiveObject(); | |
| this.activeObjectId = null; | |
| }, | |
| deleteAll: () => { | |
| exitCropOnAction(); | |
| this.clearObjects(); | |
| this.ui.changeHelpButtonEnabled('delete', false); | |
| this.ui.changeHelpButtonEnabled('deleteAll', false); | |
| }, | |
| load: (file) => { | |
| if (!isSupportFileApi()) { | |
| alert('This browser does not support file-api'); | |
| } | |
| this.ui.initializeImgUrl = URL.createObjectURL(file); | |
| this.loadImageFromFile(file) | |
| .then((sizeValue) => { | |
| exitCropOnAction(); | |
| initFilterState(); | |
| this.clearUndoStack(); | |
| this.ui.activeMenuEvent(); | |
| this.ui.resizeEditor({ imageSize: sizeValue }); | |
| this._clearHistory(); | |
| this._invoker.fire(eventNames.EXECUTE_COMMAND, historyNames.LOAD_IMAGE); | |
| }) | |
| ['catch']((message) => Promise.reject(message)); | |
| }, | |
| download: () => { | |
| const dataURL = this.toDataURL(); | |
| let imageName = this.getImageName(); | |
| let blob, type, w; | |
| if (isSupportFileApi() && window.saveAs) { | |
| blob = base64ToBlob(dataURL); | |
| type = blob.type.split('/')[1]; | |
| if (imageName.split('.').pop() !== type) { | |
| imageName += `.${type}`; | |
| } | |
| saveAs(blob, imageName); // eslint-disable-line | |
| } else { | |
| w = window.open(); | |
| w.document.body.innerHTML = `<img src='${dataURL}'>`; | |
| } | |
| }, | |
| history: (event) => { | |
| this.ui.toggleHistoryMenu(event); | |
| }, | |
| zoomIn: () => { | |
| this.ui.toggleZoomButtonStatus('zoomIn'); | |
| this.deactivateAll(); | |
| toggleZoomMode(); | |
| }, | |
| zoomOut: () => { | |
| this._graphics.zoomOut(); | |
| }, | |
| hand: () => { | |
| this.ui.offZoomInButtonStatus(); | |
| this.ui.toggleZoomButtonStatus('hand'); | |
| this.deactivateAll(); | |
| toggleHandMode(); | |
| }, | |
| }, | |
| this._commonAction() | |
| ); | |
| }, | |
| /** | |
| * Icon Action | |
| * @returns {Object} actions for ui icon | |
| * @private | |
| */ | |
| _iconAction() { | |
| return extend( | |
| { | |
| changeColor: (color) => { | |
| if (this.activeObjectId) { | |
| this.changeIconColor(this.activeObjectId, color); | |
| } | |
| }, | |
| addIcon: (iconType, iconColor) => { | |
| this.startDrawingMode('ICON'); | |
| this.setDrawingIcon(iconType, iconColor); | |
| }, | |
| cancelAddIcon: () => { | |
| this.ui.icon.clearIconType(); | |
| this.changeSelectableAll(true); | |
| this.changeCursor('default'); | |
| this.stopDrawingMode(); | |
| }, | |
| registerDefaultIcons: (type, path) => { | |
| const iconObj = {}; | |
| iconObj[type] = path; | |
| this.registerIcons(iconObj); | |
| }, | |
| registerCustomIcon: (imgUrl, file) => { | |
| const imagetracer = new Imagetracer(); | |
| imagetracer.imageToSVG( | |
| imgUrl, | |
| (svgstr) => { | |
| const [, svgPath] = svgstr.match(/path[^>]*d="([^"]*)"/); | |
| const iconObj = {}; | |
| iconObj[file.name] = svgPath; | |
| this.registerIcons(iconObj); | |
| this.addIcon(file.name, { | |
| left: 100, | |
| top: 100, | |
| }); | |
| }, | |
| Imagetracer.tracerDefaultOption() | |
| ); | |
| }, | |
| }, | |
| this._commonAction() | |
| ); | |
| }, | |
| /** | |
| * Draw Action | |
| * @returns {Object} actions for ui draw | |
| * @private | |
| */ | |
| _drawAction() { | |
| return extend( | |
| { | |
| setDrawMode: (type, settings) => { | |
| this.stopDrawingMode(); | |
| if (type === 'free') { | |
| this.startDrawingMode('FREE_DRAWING', settings); | |
| } else { | |
| this.startDrawingMode('LINE_DRAWING', settings); | |
| } | |
| }, | |
| setColor: (color) => { | |
| this.setBrush({ | |
| color, | |
| }); | |
| }, | |
| }, | |
| this._commonAction() | |
| ); | |
| }, | |
| /** | |
| * Mask Action | |
| * @returns {Object} actions for ui mask | |
| * @private | |
| */ | |
| _maskAction() { | |
| return extend( | |
| { | |
| loadImageFromURL: (imgUrl, file) => { | |
| return this.loadImageFromURL(this.toDataURL(), 'FilterImage').then(() => { | |
| this.addImageObject(imgUrl).then(() => { | |
| URL.revokeObjectURL(file); | |
| }); | |
| this._invoker.fire(eventNames.EXECUTE_COMMAND, historyNames.LOAD_MASK_IMAGE); | |
| }); | |
| }, | |
| applyFilter: () => { | |
| this.applyFilter('mask', { | |
| maskObjId: this.activeObjectId, | |
| }); | |
| }, | |
| }, | |
| this._commonAction() | |
| ); | |
| }, | |
| /** | |
| * Text Action | |
| * @returns {Object} actions for ui text | |
| * @private | |
| */ | |
| _textAction() { | |
| return extend( | |
| { | |
| changeTextStyle: (styleObj, isSilent) => { | |
| if (this.activeObjectId) { | |
| this.changeTextStyle(this.activeObjectId, styleObj, isSilent); | |
| } | |
| }, | |
| }, | |
| this._commonAction() | |
| ); | |
| }, | |
| /** | |
| * Rotate Action | |
| * @returns {Object} actions for ui rotate | |
| * @private | |
| */ | |
| _rotateAction() { | |
| return extend( | |
| { | |
| rotate: (angle, isSilent) => { | |
| this.rotate(angle, isSilent); | |
| this.ui.resizeEditor(); | |
| this.ui.rotate.setRangeBarAngle('rotate', angle); | |
| }, | |
| setAngle: (angle, isSilent) => { | |
| this.setAngle(angle, isSilent); | |
| this.ui.resizeEditor(); | |
| this.ui.rotate.setRangeBarAngle('setAngle', angle); | |
| }, | |
| }, | |
| this._commonAction() | |
| ); | |
| }, | |
| /** | |
| * Shape Action | |
| * @returns {Object} actions for ui shape | |
| * @private | |
| */ | |
| _shapeAction() { | |
| return extend( | |
| { | |
| changeShape: (changeShapeObject, isSilent) => { | |
| if (this.activeObjectId) { | |
| this.changeShape(this.activeObjectId, changeShapeObject, isSilent); | |
| } | |
| }, | |
| setDrawingShape: (shapeType) => { | |
| this.setDrawingShape(shapeType); | |
| }, | |
| }, | |
| this._commonAction() | |
| ); | |
| }, | |
| /** | |
| * Crop Action | |
| * @returns {Object} actions for ui crop | |
| * @private | |
| */ | |
| _cropAction() { | |
| return extend( | |
| { | |
| crop: () => { | |
| const cropRect = this.getCropzoneRect(); | |
| if (cropRect && !isEmptyCropzone(cropRect)) { | |
| this.crop(cropRect) | |
| .then(() => { | |
| this.stopDrawingMode(); | |
| this.ui.resizeEditor(); | |
| this.ui.changeMenu('crop'); | |
| this._invoker.fire(eventNames.EXECUTE_COMMAND, historyNames.CROP); | |
| }) | |
| ['catch']((message) => Promise.reject(message)); | |
| } | |
| }, | |
| cancel: () => { | |
| this.stopDrawingMode(); | |
| this.ui.changeMenu('crop'); | |
| }, | |
| /* eslint-disable */ | |
| preset: (presetType) => { | |
| switch (presetType) { | |
| case 'preset-square': | |
| this.setCropzoneRect(1 / 1); | |
| break; | |
| case 'preset-3-2': | |
| this.setCropzoneRect(3 / 2); | |
| break; | |
| case 'preset-4-3': | |
| this.setCropzoneRect(4 / 3); | |
| break; | |
| case 'preset-5-4': | |
| this.setCropzoneRect(5 / 4); | |
| break; | |
| case 'preset-7-5': | |
| this.setCropzoneRect(7 / 5); | |
| break; | |
| case 'preset-16-9': | |
| this.setCropzoneRect(16 / 9); | |
| break; | |
| default: | |
| this.setCropzoneRect(); | |
| this.ui.crop.changeApplyButtonStatus(false); | |
| break; | |
| } | |
| }, | |
| }, | |
| this._commonAction() | |
| ); | |
| }, | |
| /** | |
| * Resize Action | |
| * @returns {Object} actions for ui resize | |
| * @private | |
| */ | |
| _resizeAction() { | |
| return extend( | |
| { | |
| getCurrentDimensions: () => this._graphics.getCurrentDimensions(), | |
| preview: (actor, value, lockState) => { | |
| const currentDimensions = this._graphics.getCurrentDimensions(); | |
| const calcAspectRatio = () => currentDimensions.width / currentDimensions.height; | |
| let dimensions = {}; | |
| switch (actor) { | |
| case 'width': | |
| dimensions.width = value; | |
| if (lockState) { | |
| dimensions.height = value / calcAspectRatio(); | |
| } else { | |
| dimensions.height = currentDimensions.height; | |
| } | |
| break; | |
| case 'height': | |
| dimensions.height = value; | |
| if (lockState) { | |
| dimensions.width = value * calcAspectRatio(); | |
| } else { | |
| dimensions.width = currentDimensions.width; | |
| } | |
| break; | |
| default: | |
| dimensions = currentDimensions; | |
| } | |
| this._graphics.resize(dimensions).then(() => { | |
| this.ui.resizeEditor(); | |
| }); | |
| if (lockState) { | |
| this.ui.resize.setWidthValue(dimensions.width); | |
| this.ui.resize.setHeightValue(dimensions.height); | |
| } | |
| }, | |
| resize: (dimensions = null) => { | |
| if (!dimensions) { | |
| dimensions = this._graphics.getCurrentDimensions(); | |
| } | |
| this.resize(dimensions) | |
| .then(() => { | |
| this._graphics.setOriginalDimensions(dimensions); | |
| this.stopDrawingMode(); | |
| this.ui.resizeEditor(); | |
| this.ui.changeMenu('resize'); | |
| }) | |
| ['catch']((message) => Promise.reject(message)); | |
| }, | |
| reset: (standByMode = false) => { | |
| const dimensions = this._graphics.getOriginalDimensions(); | |
| this.ui.resize.setWidthValue(dimensions.width, true); | |
| this.ui.resize.setHeightValue(dimensions.height, true); | |
| this._graphics.resize(dimensions).then(() => { | |
| if (!standByMode) { | |
| this.stopDrawingMode(); | |
| this.ui.resizeEditor(); | |
| this.ui.changeMenu('resize'); | |
| } | |
| }); | |
| }, | |
| }, | |
| this._commonAction() | |
| ); | |
| }, | |
| /** | |
| * Flip Action | |
| * @returns {Object} actions for ui flip | |
| * @private | |
| */ | |
| _flipAction() { | |
| return extend( | |
| { | |
| flip: (flipType) => this[flipType](), | |
| }, | |
| this._commonAction() | |
| ); | |
| }, | |
| /** | |
| * Filter Action | |
| * @returns {Object} actions for ui filter | |
| * @private | |
| */ | |
| _filterAction() { | |
| return extend( | |
| { | |
| applyFilter: (applying, type, options, isSilent) => { | |
| if (applying) { | |
| this.applyFilter(type, options, isSilent); | |
| } else if (this.hasFilter(type)) { | |
| this.removeFilter(type); | |
| } | |
| }, | |
| }, | |
| this._commonAction() | |
| ); | |
| }, | |
| /** | |
| * Image Editor Event Observer | |
| */ | |
| setReAction() { | |
| this.on({ | |
| undoStackChanged: (length) => { | |
| if (length) { | |
| this.ui.changeHelpButtonEnabled('undo', true); | |
| this.ui.changeHelpButtonEnabled('reset', true); | |
| } else { | |
| this.ui.changeHelpButtonEnabled('undo', false); | |
| this.ui.changeHelpButtonEnabled('reset', false); | |
| } | |
| this.ui.resizeEditor(); | |
| }, | |
| redoStackChanged: (length) => { | |
| if (length) { | |
| this.ui.changeHelpButtonEnabled('redo', true); | |
| } else { | |
| this.ui.changeHelpButtonEnabled('redo', false); | |
| } | |
| this.ui.resizeEditor(); | |
| }, | |
| /* eslint-disable complexity */ | |
| objectActivated: (obj) => { | |
| this.activeObjectId = obj.id; | |
| this.ui.changeHelpButtonEnabled('delete', true); | |
| this.ui.changeHelpButtonEnabled('deleteAll', true); | |
| if (obj.type === 'cropzone') { | |
| this.ui.crop.changeApplyButtonStatus(true); | |
| } else if (['rect', 'circle', 'triangle'].indexOf(obj.type) > -1) { | |
| this.stopDrawingMode(); | |
| if (this.ui.submenu !== 'shape') { | |
| this.ui.changeMenu('shape', false, false); | |
| } | |
| this.ui.shape.setShapeStatus({ | |
| strokeColor: obj.stroke, | |
| strokeWidth: obj.strokeWidth, | |
| fillColor: obj.fill, | |
| }); | |
| this.ui.shape.setMaxStrokeValue(Math.min(obj.width, obj.height)); | |
| } else if (obj.type === 'path' || obj.type === 'line') { | |
| if (this.ui.submenu !== 'draw') { | |
| this.ui.changeMenu('draw', false, false); | |
| this.ui.draw.changeStandbyMode(); | |
| } | |
| } else if (['i-text', 'text'].indexOf(obj.type) > -1) { | |
| if (this.ui.submenu !== 'text') { | |
| this.ui.changeMenu('text', false, false); | |
| } | |
| this.ui.text.setTextStyleStateOnAction(obj); | |
| } else if (obj.type === 'icon') { | |
| this.stopDrawingMode(); | |
| if (this.ui.submenu !== 'icon') { | |
| this.ui.changeMenu('icon', false, false); | |
| } | |
| this.ui.icon.setIconPickerColor(obj.fill); | |
| } | |
| }, | |
| /* eslint-enable complexity */ | |
| addText: (pos) => { | |
| const { textColor: fill, fontSize, fontStyle, fontWeight, underline } = this.ui.text; | |
| const fontFamily = 'Noto Sans'; | |
| this.addText('Double Click', { | |
| position: pos.originPosition, | |
| styles: { fill, fontSize, fontFamily, fontStyle, fontWeight, underline }, | |
| }).then(() => { | |
| this.changeCursor('default'); | |
| }); | |
| }, | |
| addObjectAfter: (obj) => { | |
| if (obj.type === 'icon') { | |
| this.ui.icon.changeStandbyMode(); | |
| } else if (['rect', 'circle', 'triangle'].indexOf(obj.type) > -1) { | |
| this.ui.shape.setMaxStrokeValue(Math.min(obj.width, obj.height)); | |
| this.ui.shape.changeStandbyMode(); | |
| } | |
| }, | |
| objectScaled: (obj) => { | |
| if (['i-text', 'text'].indexOf(obj.type) > -1) { | |
| this.ui.text.fontSize = toInteger(obj.fontSize); | |
| } else if (['rect', 'circle', 'triangle'].indexOf(obj.type) >= 0) { | |
| const { width, height } = obj; | |
| const strokeValue = this.ui.shape.getStrokeValue(); | |
| if (width < strokeValue) { | |
| this.ui.shape.setStrokeValue(width); | |
| } | |
| if (height < strokeValue) { | |
| this.ui.shape.setStrokeValue(height); | |
| } | |
| } | |
| }, | |
| selectionCleared: () => { | |
| this.activeObjectId = null; | |
| if (this.ui.submenu === 'text') { | |
| this.changeCursor('text'); | |
| } else if (!includes(['draw', 'crop', 'resize'], this.ui.submenu)) { | |
| this.stopDrawingMode(); | |
| } | |
| }, | |
| }); | |
| }, | |
| /** | |
| * History Action | |
| * @returns {Object} history actions for ui | |
| * @private | |
| */ | |
| _historyAction() { | |
| return { | |
| undo: (count) => this.undo(count), | |
| redo: (count) => this.redo(count), | |
| }; | |
| }, | |
| /** | |
| * Common Action | |
| * @returns {Object} common actions for ui | |
| * @private | |
| */ | |
| _commonAction() { | |
| const { TEXT, CROPPER, SHAPE, ZOOM, RESIZE } = drawingModes; | |
| return { | |
| modeChange: (menu) => { | |
| switch (menu) { | |
| case drawingMenuNames.TEXT: | |
| this._changeActivateMode(TEXT); | |
| break; | |
| case drawingMenuNames.CROP: | |
| this.startDrawingMode(CROPPER); | |
| break; | |
| case drawingMenuNames.SHAPE: | |
| this._changeActivateMode(SHAPE); | |
| this.setDrawingShape(this.ui.shape.type, this.ui.shape.options); | |
| break; | |
| case drawingMenuNames.ZOOM: | |
| this.startDrawingMode(ZOOM); | |
| break; | |
| case drawingMenuNames.RESIZE: | |
| this.startDrawingMode(RESIZE); | |
| break; | |
| default: | |
| break; | |
| } | |
| }, | |
| deactivateAll: this.deactivateAll.bind(this), | |
| changeSelectableAll: this.changeSelectableAll.bind(this), | |
| discardSelection: this.discardSelection.bind(this), | |
| stopDrawingMode: this.stopDrawingMode.bind(this), | |
| }; | |
| }, | |
| /** | |
| * Mixin | |
| * @param {ImageEditor} ImageEditor instance | |
| */ | |
| mixin(ImageEditor) { | |
| extend(ImageEditor.prototype, this); | |
| }, | |
| }; | |