import { fabric } from 'fabric' ; import { union } from 'lodash' ; import { uuid } from 'uuidv4' ; import warning from 'warning' ; import { AlignmentHandler , AnimationHandler , ChartHandler , ContextmenuHandler , CropHandler , CustomHandler , DrawingHandler , ElementHandler , EventHandler , GridHandler , GuidelineHandler , ImageHandler , InteractionHandler , LinkHandler , NodeHandler , PortHandler , ShortcutHandler , TooltipHandler , TransactionHandler , WorkareaHandler , ZoomHandler , } from '.' ; import CanvasObject from '../CanvasObject' ; import { defaults } from '../constants' ; import { LinkObject } from '../objects/Link' ; import { NodeObject } from '../objects/Node' ; import { PortObject } from '../objects/Port' ; import { CanvasOption , FabricCanvas , FabricElement , FabricGroup , FabricImage , FabricObject , FabricObjectOption , FabricObjects , GridOption , GuidelineOption , InteractionMode , KeyEvent , WorkareaObject , WorkareaOption , } from '../utils' ; import { LinkOption } from './LinkHandler' ; import { TransactionEvent } from './TransactionHandler' ; export interface HandlerCallback { /**
     * When has been added object in Canvas, Called function
     *
     */ onAdd ? : ( object : FabricObject ) => void ; /**
     * Return contextmenu element
     *
     */ onContext ? : ( el : HTMLDivElement , e : React . MouseEvent , target ? : FabricObject ) => Promise < any > | any ; /**
     * Return tooltip element
     *
     */ onTooltip ? : ( el : HTMLDivElement , target ? : FabricObject ) => Promise < any > | any ; /**
     * When zoom, Called function
     */ onZoom ? : ( zoomRatio : number ) => void ; /**
     * When clicked object, Called function
     *
     */ onClick ? : ( canvas : FabricCanvas , target : FabricObject ) => void ; /**
     * When double clicked object, Called function
     *
     */ onDblClick ? : ( canvas : FabricCanvas , target : FabricObject ) => void ; /**
     * When modified object, Called function
     */ onModified ? : ( target : FabricObject ) => void ; /**
     * When select object, Called function
     *
     */ onSelect ? : ( target : FabricObject ) => void ; /**
     * When has been removed object in Canvas, Called function
     *
     */ onRemove ? : ( target : FabricObject ) => void ; /**
     * When has been undo or redo, Called function
     *
     */ onTransaction ? : ( transaction : TransactionEvent ) => void ; /**
     * When has been changed interaction mode, Called function
     *
     */ onInteraction ? : ( interactionMode : InteractionMode ) => void ; /**
     * When canvas has been loaded
     *
     */ onLoad ? : ( handler : Handler , canvas ? : fabric . Canvas ) => void ; } export interface HandlerOption { type { string } id ? : string ; type { FabricCanvas } canvas ? : FabricCanvas ; type { HTMLDivElement } container ? : HTMLDivElement ; type { boolean } editable ? : boolean ; type { InteractionMode } interactionMode ? : InteractionMode ; type { string [ ] } propertiesToInclude ? : string [ ] ; type { number } minZoom ? : number ; type { number } maxZoom ? : number ; type { WorkareaOption } workareaOption ? : WorkareaOption ; type { CanvasOption } canvasOption ? : CanvasOption ; type { GridOption } gridOption ? : GridOption ; type { FabricObjectOption } objectOption ? : FabricObjectOption ; type { GuidelineOption } guidelineOption ? : GuidelineOption ; type { boolean } zoomEnabled ? : boolean ; type { Partial < FabricObjectOption < fabric . ActiveSelection > > } activeSelectionOption ? : Partial < FabricObjectOption < fabric . ActiveSelection > > ; type { number } width ? : number ; type { number } height ? : number ; type { KeyEvent } keyEvent ? : KeyEvent ; type { { [ key : string ] : any } } fabricObjects ? : FabricObjects ; handlers ? : { [ key ] : CustomHandler ; } ; [ key ] : any ; } export type HandlerOptions = HandlerOption & HandlerCallback ; class implements HandlerOptions class Handler implements HandlerOptions { public id : string ; public canvas : FabricCanvas ; public workarea : WorkareaObject ; public container : HTMLDivElement ; public editable : boolean ; public interactionMode : InteractionMode ; public minZoom : number ; public maxZoom : number ; public propertiesToInclude ? : string [ ] = defaults . propertiesToInclude ; public workareaOption ? : WorkareaOption = defaults . workareaOption ; public canvasOption ? : CanvasOption = defaults . canvasOption ; public gridOption ? : GridOption = defaults . gridOption ; public objectOption ? : FabricObjectOption = defaults . objectOption ; public guidelineOption ? : GuidelineOption = defaults . guidelineOption ; public keyEvent ? : KeyEvent = defaults . keyEvent ; public activeSelectionOption ? : Partial < FabricObjectOption < fabric . ActiveSelection > > = defaults . activeSelectionOption ; public fabricObjects ? : FabricObjects = CanvasObject ; public zoomEnabled ? : boolean ; public width ? : number ; public height ? : number ; public onAdd ? : ( object : FabricObject ) => void ; public onContext ? : ( el : HTMLDivElement , e : React . MouseEvent , target ? : FabricObject ) => Promise < any > ; public onTooltip ? : ( el : HTMLDivElement , target ? : FabricObject ) => Promise < any > ; public onZoom ? : ( zoomRatio : number ) => void ; public onClick ? : ( canvas : FabricCanvas , target : FabricObject ) => void ; public onDblClick ? : ( canvas : FabricCanvas , target : FabricObject ) => void ; public onModified ? : ( target : FabricObject ) => void ; public onSelect ? : ( target : FabricObject ) => void ; public onRemove ? : ( target : FabricObject ) => void ; public onTransaction ? : ( transaction : TransactionEvent ) => void ; public onInteraction ? : ( interactionMode : InteractionMode ) => void ; public onLoad ? : ( handler : Handler , canvas ? : fabric . Canvas ) => void ; public imageHandler : ImageHandler ; public chartHandler : ChartHandler ; public elementHandler : ElementHandler ; public cropHandler : CropHandler ; public animationHandler : AnimationHandler ; public contextmenuHandler : ContextmenuHandler ; public tooltipHandler : TooltipHandler ; public zoomHandler : ZoomHandler ; public workareaHandler : WorkareaHandler ; public interactionHandler : InteractionHandler ; public transactionHandler : TransactionHandler ; public gridHandler : GridHandler ; public portHandler : PortHandler ; public linkHandler : LinkHandler ; public nodeHandler : NodeHandler ; public alignmentHandler : AlignmentHandler ; public guidelineHandler : GuidelineHandler ; public eventHandler : EventHandler ; public drawingHandler : DrawingHandler ; public shortcutHandler : ShortcutHandler ; public handlers : { [ key ] : CustomHandler ; } = { } ; public objectMap : Record < string , FabricObject > = { } ; public objects : FabricObject [ ] ; public activeLine ? : any ; public activeShape ? : any ; public zoom = 1 ; public prevTarget ? : FabricObject ; public target ? : FabricObject ; public pointArray ? : any [ ] ; public lineArray ? : any [ ] ; public isCut = false ; private isRequsetAnimFrame = false ; private requestFrame : any ; private type { * } private clipboard : any ; constructor ( options ) { this . initialize ( options ) ; } author salgum1114
     *  param { HandlerOptions } options public initialize ( options ) { this . initOption ( options ) ; this . initCallback ( options ) ; this . initHandler ( ) ; } param { HandlerOptions } options public initOption = ( options ) => { this . id = options . id ; this . canvas = options . canvas ; this . container = options . container ; this . editable = options . editable ; this . interactionMode = options . interactionMode ; this . minZoom = options . minZoom ; this . maxZoom = options . maxZoom ; this . zoomEnabled = options . zoomEnabled ; this . width = options . width ; this . height = options . height ; this . objects = [ ] ; this . setPropertiesToInclude ( options . propertiesToInclude ) ; this . setWorkareaOption ( options . workareaOption ) ; this . setCanvasOption ( options . canvasOption ) ; this . setGridOption ( options . gridOption ) ; this . setObjectOption ( options . objectOption ) ; this . setFabricObjects ( options . fabricObjects ) ; this . setGuidelineOption ( options . guidelineOption ) ; this . setActiveSelectionOption ( options . activeSelectionOption ) ; this . setKeyEvent ( options . keyEvent ) ; } ; param { HandlerOptions } options public initCallback = ( options ) => { this . onAdd = options . onAdd ; this . onTooltip = options . onTooltip ; this . onZoom = options . onZoom ; this . onContext = options . onContext ; this . onClick = options . onClick ; this . onModified = options . onModified ; this . onDblClick = options . onDblClick ; this . onSelect = options . onSelect ; this . onRemove = options . onRemove ; this . onTransaction = options . onTransaction ; this . onInteraction = options . onInteraction ; this . onLoad = options . onLoad ; } ; /**
     * Initialize handlers
     *
     */ public initHandler = ( ) => { this . workareaHandler = new WorkareaHandler ( this ) ; this . imageHandler = new ImageHandler ( this ) ; this . chartHandler = new ChartHandler ( this ) ; this . elementHandler = new ElementHandler ( this ) ; this . cropHandler = new CropHandler ( this ) ; this . animationHandler = new AnimationHandler ( this ) ; this . contextmenuHandler = new ContextmenuHandler ( this ) ; this . tooltipHandler = new TooltipHandler ( this ) ; this . zoomHandler = new ZoomHandler ( this ) ; this . interactionHandler = new InteractionHandler ( this ) ; this . transactionHandler = new TransactionHandler ( this ) ; this . gridHandler = new GridHandler ( this ) ; this . portHandler = new PortHandler ( this ) ; this . linkHandler = new LinkHandler ( this ) ; this . nodeHandler = new NodeHandler ( this ) ; this . alignmentHandler = new AlignmentHandler ( this ) ; this . guidelineHandler = new GuidelineHandler ( this ) ; this . eventHandler = new EventHandler ( this ) ; this . drawingHandler = new DrawingHandler ( this ) ; this . shortcutHandler = new ShortcutHandler ( this ) ; } ; returns { FabricObject [ ] } public getObjects = ( ) : FabricObject [ ] => { const objects = this . canvas . getObjects ( ) . filter ( ( obj ) => { if ( obj . id === 'workarea' ) { return false ; } else if ( obj . id === 'grid' ) { return false ; } else if ( obj . superType === 'port' ) { return false ; } else if ( ! obj . id ) { return false ; } return true ; } ) as FabricObject [ ] ; if ( objects . length ) { objects . forEach ( obj => ( this . objectMap [ obj . id ] = obj ) ) ; } else { this . objectMap = { } ; } return objects ; } ; param { keyof FabricObject } key param { * } value returns public set = ( key , value ) => { const activeObject = this . canvas . getActiveObject ( ) as FabricObject ; if ( ! activeObject ) { return ; } if ( activeObject . type === 'svg' && ( key === 'fill' || key === 'stroke' ) ) { ( activeObject as FabricGroup ) . _objects . forEach ( obj => obj . set ( key , value ) ) ; } activeObject . set ( key , value ) ; activeObject . setCoords ( ) ; this . canvas . requestRenderAll ( ) ; const { id , superType , type , player , width , height } = activeObject as any ; if ( superType === 'element' ) { if ( key === 'visible' ) { if ( value ) { activeObject . element . style . display = 'block' ; } else { activeObject . element . style . display = 'none' ; } } const el = this . elementHandler . findById ( id ) ; this . elementHandler . setScaleOrAngle ( el , activeObject ) ; this . elementHandler . setSize ( el , activeObject ) ; this . elementHandler . setPosition ( el , activeObject ) ; if ( type === 'video' && player ) { player . setPlayerSize ( width , height ) ; } } const { onModified } = this ; if ( onModified ) { onModified ( activeObject ) ; } } ; param { Partial < FabricObject > } option returns public setObject = ( option ) => { const activeObject = this . canvas . getActiveObject ( ) as any ; if ( ! activeObject ) { return ; } Object . keys ( option ) . forEach ( key => { if ( option [ key ] !== activeObject [ key ] ) { activeObject . set ( key , option [ key ] ) ; activeObject . setCoords ( ) ; } } ) ; this . canvas . requestRenderAll ( ) ; const { id , superType , type , player , width , height } = activeObject ; if ( superType === 'element' ) { if ( 'visible' in option ) { if ( option . visible ) { activeObject . element . style . display = 'block' ; } else { activeObject . element . style . display = 'none' ; } } const el = this . elementHandler . findById ( id ) ; this . elementHandler . setScaleOrAngle ( el , activeObject ) ; this . elementHandler . setSize ( el , activeObject ) ; this . elementHandler . setPosition ( el , activeObject ) ; if ( type === 'video' && player ) { player . setPlayerSize ( width , height ) ; } } const { onModified } = this ; if ( onModified ) { onModified ( activeObject ) ; } } ; param { FabricObject } obj param { string } key param { * } value returns public setByObject = ( obj , key , value ) => { if ( ! obj ) { return ; } if ( obj . type === 'svg' ) { if ( key === 'fill' ) { obj . setFill ( value ) ; } else if ( key === 'stroke' ) { obj . setStroke ( value ) ; } } obj . set ( key , value ) ; obj . setCoords ( ) ; this . canvas . renderAll ( ) ; const { id , superType , type , player , width , height } = obj as any ; if ( superType === 'element' ) { if ( key === 'visible' ) { if ( value ) { obj . element . style . display = 'block' ; } else { obj . element . style . display = 'none' ; } } const el = this . elementHandler . findById ( id ) ; this . elementHandler . setScaleOrAngle ( el , obj ) ; this . elementHandler . setSize ( el , obj ) ; this . elementHandler . setPosition ( el , obj ) ; if ( type === 'video' && player ) { player . setPlayerSize ( width , height ) ; } } const { onModified } = this ; if ( onModified ) { onModified ( obj ) ; } } ; param { string } id param { string } key param { * } value public setById = ( id , key , value ) => { const findObject = this . findById ( id ) ; this . setByObject ( findObject , key , value ) ; } ; param { FabricObject } obj param { FabricObjectOption } option returns public setByPartial = ( obj , option ) => { if ( ! obj ) { return ; } if ( obj . type === 'svg' ) { if ( option . fill ) { obj . setFill ( option . fill ) ; } else if ( option . stroke ) { obj . setStroke ( option . stroke ) ; } } obj . set ( option ) ; obj . setCoords ( ) ; this . canvas . renderAll ( ) ; const { id , superType , type , player , width , height } = obj as any ; if ( superType === 'element' ) { if ( 'visible' in option ) { if ( option . visible ) { obj . element . style . display = 'block' ; } else { obj . element . style . display = 'none' ; } } const el = this . elementHandler . findById ( id ) ; this . elementHandler . setScaleOrAngle ( el , obj ) ; this . elementHandler . setSize ( el , obj ) ; this . elementHandler . setPosition ( el , obj ) ; if ( type === 'video' && player ) { player . setPlayerSize ( width , height ) ; } } } ; param { fabric . Shadow } option returns public setShadow = ( option ) => { const activeObject = this . canvas . getActiveObject ( ) as FabricObject ; if ( ! activeObject ) { return ; } activeObject . set ( 'shadow' , new fabric . Shadow ( option ) ) ; this . canvas . requestRenderAll ( ) ; const { onModified } = this ; if ( onModified ) { onModified ( activeObject ) ; } } ; param { FabricImage } obj param { ( File | string ) } source returns public setImage = ( obj , source ? ) => { if ( ! source ) { this . loadImage ( obj , null ) ; obj . set ( 'file' , null ) ; obj . set ( 'src' , null ) ; return ; } if ( source instanceof File ) { const reader = new FileReader ( ) ; reader . onload = ( ) => { this . loadImage ( obj , reader . result as string ) ; obj . set ( 'file' , source ) ; obj . set ( 'src' , null ) ; } ; reader . readAsDataURL ( source ) ; } else { this . loadImage ( obj , source ) ; obj . set ( 'file' , null ) ; obj . set ( 'src' , source ) ; } } ; param { string } id param { * } source public setImageById = ( id , source ) => { const findObject = this . findById ( id ) as FabricImage ; this . setImage ( findObject , source ) ; } ; param { boolean } visible returns public setVisible = ( visible ? ) => { const activeObject = this . canvas . getActiveObject ( ) as FabricElement ; if ( ! activeObject ) { return ; } if ( activeObject . superType === 'element' ) { if ( visible ) { activeObject . element . style . display = 'block' ; } else { activeObject . element . style . display = 'none' ; } } activeObject . set ( { visible , } ) ; this . canvas . renderAll ( ) ; } ; param { FabricObject } obj param { boolean } centered public centerObject = ( obj , centered ? ) => { if ( centered ) { this . canvas . centerObject ( obj ) ; obj . setCoords ( ) ; } else { this . setByPartial ( obj , { left : obj . left / this . canvas . getZoom ( ) - obj . width / 2 - this . canvas . viewportTransform [ 4 ] / this . canvas . getZoom ( ) , top : obj . top / this . canvas . getZoom ( ) - obj . height / 2 - this . canvas . viewportTransform [ 5 ] / this . canvas . getZoom ( ) , } ) ; } } ; param { FabricObjectOption } obj param { boolean } centered param { boolean } loaded returns public add = ( obj , centered = true , loaded = false ) => { const { editable , onAdd , gridOption , objectOption } = this ; const option = { hasControls : editable , hasBorders : editable , selectable : editable , lockMovementX : ! editable , lockMovementY : ! editable , hoverCursor : ! editable ? 'pointer' : 'move' , } ; if ( obj . type === 'i-text' ) { option . editable = false ; } else { option . editable = editable ; } if ( editable && this . workarea . layout === 'fullscreen' ) { option . scaleX = this . workarea . scaleX ; option . scaleY = this . workarea . scaleY ; } const newOption = Object . assign ( { } , objectOption , obj , { container : this . container . id , editable , } , option ) ; if ( obj . superType === 'link' ) { return this . linkHandler . create ( newOption , loaded ) ; } let createdObj ; if ( obj . type === 'image' ) { createdObj = this . addImage ( newOption ) ; } else if ( obj . type === 'group' ) { const objects = this . addGroup ( newOption , centered , loaded ) ; const groupOption = Object . assign ( { } , newOption , { objects , name : 'New Group' } ) ; createdObj = this . fabricObjects [ obj . type ] . create ( groupOption ) ; } else { createdObj = this . fabricObjects [ obj . type ] . create ( newOption ) ; } this . canvas . add ( createdObj ) ; this . objects = this . getObjects ( ) ; if ( ! editable && ! ( obj . superType === 'element' ) ) { createdObj . on ( 'mousedown' , this . eventHandler . object . mousedown ) ; } if ( createdObj . dblclick ) { createdObj . on ( 'mousedblclick' , this . eventHandler . object . mousedblclick ) ; } if ( this . objects . some ( object => object . type === 'gif' ) ) { this . startRequestAnimFrame ( ) ; } else { this . stopRequestAnimFrame ( ) ; } if ( obj . superType !== 'drawing' && obj . superType !== 'link' && editable && ! loaded ) { this . centerObject ( createdObj , centered ) ; } if ( createdObj . superType === 'node' ) { this . portHandler . create ( createdObj as NodeObject ) ; if ( createdObj . iconButton ) { this . canvas . add ( createdObj . iconButton ) ; } } if ( ! editable && createdObj . animation && createdObj . animation . autoplay ) { this . animationHandler . play ( createdObj . id ) ; } if ( createdObj . superType === 'node' ) { createdObj . set ( 'shadow' , { color : createdObj . stroke , } as fabric . Shadow ) ; } if ( gridOption . enabled ) { this . gridHandler . setCoords ( createdObj ) ; } if ( ! this . transactionHandler . active && ! loaded ) { this . transactionHandler . save ( 'add' ) ; } if ( onAdd && editable && ! loaded ) { onAdd ( createdObj ) ; } return createdObj ; } ; param { FabricGroup } obj param { boolean } centered param { boolean } loaded returns public addGroup = ( obj , centered = true , loaded = false ) => { return obj . objects . map ( child => { return this . add ( child , centered , loaded ) ; } ) ; } ; param { FabricImage } obj returns public addImage = ( obj ) => { const { objectOption } = this ; const { filters = [ ] , ... otherOption } = obj ; const image = new Image ( ) ; if ( obj . src ) { image . src = obj . src ; } const createdObj = new fabric . Image ( image , { ... objectOption , ... otherOption , } ) as FabricImage ; createdObj . set ( { filters : this . imageHandler . createFilters ( filters ) , } ) ; this . setImage ( createdObj , obj . src || obj . file ) ; return createdObj ; } ; param { FabricObject } target returns { any } public remove = ( target ? ) => { const activeObject = target || ( this . canvas . getActiveObject ( ) as any ) ; if ( this . prevTarget && this . prevTarget . superType === 'link' ) { this . linkHandler . remove ( this . prevTarget as LinkObject ) ; if ( ! this . transactionHandler . active ) { this . transactionHandler . save ( 'remove' ) ; } return ; } if ( ! activeObject ) { return ; } if ( typeof activeObject . deletable !== 'undefined' && ! activeObject . deletable ) { return ; } if ( activeObject . type !== 'activeSelection' ) { this . canvas . discardActiveObject ( ) ; if ( activeObject . superType === 'element' ) { this . elementHandler . removeById ( activeObject . id ) ; } if ( activeObject . superType === 'link' ) { this . linkHandler . remove ( activeObject ) ; } else if ( activeObject . superType === 'node' ) { if ( activeObject . toPort ) { if ( activeObject . toPort . links . length ) { activeObject . toPort . links . forEach ( ( link ) => { this . linkHandler . remove ( link , 'from' ) ; } ) ; } this . canvas . remove ( activeObject . toPort ) ; } if ( activeObject . fromPort && activeObject . fromPort . length ) { activeObject . fromPort . forEach ( ( port ) => { if ( port . links . length ) { port . links . forEach ( ( link ) => { this . linkHandler . remove ( link , 'to' ) ; } ) ; } this . canvas . remove ( port ) ; } ) ; } } this . canvas . remove ( activeObject ) ; } else { const { _objects : activeObjects } = activeObject ; const existDeleted = activeObjects . some ( ( obj ) => typeof obj . deletable !== 'undefined' && ! obj . deletable ) ; if ( existDeleted ) { return ; } this . canvas . discardActiveObject ( ) ; activeObjects . forEach ( ( obj ) => { if ( obj . superType === 'element' ) { this . elementHandler . removeById ( obj . id ) ; } else if ( obj . superType === 'node' ) { if ( obj . toPort ) { if ( obj . toPort . links . length ) { obj . toPort . links . forEach ( ( link ) => { this . linkHandler . remove ( link , 'from' ) ; } ) ; } this . canvas . remove ( obj . toPort ) ; } if ( obj . fromPort && obj . fromPort . length ) { obj . fromPort . forEach ( ( port ) => { if ( port . links . length ) { port . links . forEach ( ( link ) => { this . linkHandler . remove ( link , 'to' ) ; } ) ; } this . canvas . remove ( port ) ; } ) ; } } this . canvas . remove ( obj ) ; } ) ; } if ( ! this . transactionHandler . active ) { this . transactionHandler . save ( 'remove' ) ; } this . objects = this . getObjects ( ) ; const { onRemove } = this ; if ( onRemove ) { onRemove ( activeObject ) ; } } ; param { string } id public removeById = ( id ) => { const findObject = this . findById ( id ) ; if ( findObject ) { this . remove ( findObject ) ; } } ; param { string } id public removeOriginById = ( id ) => { const object = this . findOriginByIdWithIndex ( id ) ; if ( object . index > 0 ) { this . objects . splice ( object . index , 1 ) ; } } ; returns public duplicate = ( ) => { const { onAdd , propertiesToInclude , gridOption : { grid = 10 } , } = this ; const activeObject = this . canvas . getActiveObject ( ) as FabricObject ; if ( ! activeObject ) { return ; } if ( typeof activeObject . cloneable !== 'undefined' && ! activeObject . cloneable ) { return ; } activeObject . clone ( ( clonedObj ) => { this . canvas . discardActiveObject ( ) ; clonedObj . set ( { left : clonedObj . left + grid , top : clonedObj . top + grid , evented : true , } ) ; if ( clonedObj . type === 'activeSelection' ) { const activeSelection = clonedObj as fabric . ActiveSelection ; activeSelection . canvas = this . canvas ; activeSelection . forEachObject ( ( obj ) => { obj . set ( 'id' , uuid ( ) ) ; if ( obj . superType === 'node' ) { obj . set ( 'shadow' , { color : obj . stroke , } as fabric . Shadow ) ; } this . canvas . add ( obj ) ; this . objects = this . getObjects ( ) ; if ( obj . dblclick ) { obj . on ( 'mousedblclick' , this . eventHandler . object . mousedblclick ) ; } } ) ; if ( onAdd ) { onAdd ( activeSelection ) ; } activeSelection . setCoords ( ) ; } else { if ( activeObject . id === clonedObj . id ) { clonedObj . set ( 'id' , uuid ( ) ) ; } if ( clonedObj . superType === 'node' ) { clonedObj . set ( 'shadow' , { color : clonedObj . stroke , } as fabric . Shadow ) ; } this . canvas . add ( clonedObj ) ; this . objects = this . getObjects ( ) ; if ( clonedObj . dblclick ) { clonedObj . on ( 'mousedblclick' , this . eventHandler . object . mousedblclick ) ; } if ( onAdd ) { onAdd ( clonedObj ) ; } } this . canvas . setActiveObject ( clonedObj ) ; this . portHandler . create ( clonedObj as NodeObject ) ; this . canvas . requestRenderAll ( ) ; } , propertiesToInclude ) ; } ; param { string } id returns public duplicateById = ( id ) => { const { onAdd , propertiesToInclude , gridOption : { grid = 10 } , } = this ; const findObject = this . findById ( id ) ; if ( findObject ) { if ( typeof findObject . cloneable !== 'undefined' && ! findObject . cloneable ) { return false ; } findObject . clone ( ( cloned ) => { cloned . set ( { left : cloned . left + grid , top : cloned . top + grid , id : uuid ( ) , evented : true , } ) ; this . canvas . add ( cloned ) ; this . objects = this . getObjects ( ) ; if ( onAdd ) { onAdd ( cloned ) ; } if ( cloned . dblclick ) { cloned . on ( 'mousedblclick' , this . eventHandler . object . mousedblclick ) ; } this . canvas . setActiveObject ( cloned ) ; this . portHandler . create ( cloned as NodeObject ) ; this . canvas . requestRenderAll ( ) ; } , propertiesToInclude ) ; } return true ; } ; /**
     * Cut object
     *
     */ public cut = ( ) => { this . copy ( ) ; this . remove ( ) ; this . isCut = true ; } ; param { * } value public copyToClipboard = ( value ) => { const textarea = document . createElement ( 'textarea' ) ; document . body . appendChild ( textarea ) ; textarea . value = value ; textarea . select ( ) ; document . execCommand ( 'copy' ) ; document . body . removeChild ( textarea ) ; this . canvas . wrapperEl . focus ( ) ; } ; returns public copy = ( ) => { const { propertiesToInclude } = this ; const activeObject = this . canvas . getActiveObject ( ) as FabricObject ; if ( activeObject && activeObject . superType === 'link' ) { return false ; } if ( activeObject ) { if ( typeof activeObject . cloneable !== 'undefined' && ! activeObject . cloneable ) { return false ; } if ( activeObject . type === 'activeSelection' ) { const activeSelection = activeObject as fabric . ActiveSelection ; if ( activeSelection . getObjects ( ) . some ( ( obj ) => obj . superType === 'node' ) ) { if ( this . keyEvent . clipboard ) { const links = [ ] as any [ ] ; const objects = activeSelection . getObjects ( ) . map ( ( obj , index ) => { if ( typeof obj . cloneable !== 'undefined' && ! obj . cloneable ) { return null ; } if ( obj . fromPort . length ) { obj . fromPort . forEach ( ( port ) => { if ( port . links . length ) { port . links . forEach ( ( link ) => { const linkTarget = { fromNodeIndex : index , fromPortId : port . id , type : 'curvedLink' , superType : 'link' , } as any ; const findIndex = activeSelection . getObjects ( ) . findIndex ( ( compObj ) => compObj . id === link . toNode . id ) ; if ( findIndex >= 0 ) { linkTarget . toNodeIndex = findIndex ; links . push ( linkTarget ) ; } } ) ; } } ) ; } return { name : obj . name , description : obj . description , superType : obj . superType , type : obj . type , nodeClazz : obj . nodeClazz , configuration : obj . configuration , properties : { left : activeObject . left + activeObject . width / 2 + obj . left || 0 , top : activeObject . top + activeObject . height / 2 + obj . top || 0 , iconName : obj . descriptor . icon , } , } ; } ) ; this . copyToClipboard ( JSON . stringify ( objects . concat ( links ) , null , '\t' ) ) ; return true ; } this . clipboard = activeObject ; return true ; } } activeObject . clone ( ( cloned ) => { if ( this . keyEvent . clipboard ) { if ( cloned . superType === 'node' ) { const node = { name : cloned . name , description : cloned . description , superType : cloned . superType , type : cloned . type , nodeClazz : cloned . nodeClazz , configuration : cloned . configuration , properties : { left : cloned . left || 0 , top : cloned . top || 0 , iconName : cloned . descriptor . icon , } , } ; const objects = [ node ] ; this . copyToClipboard ( JSON . stringify ( objects , null , '\t' ) ) ; } else { this . copyToClipboard ( JSON . stringify ( cloned . toObject ( propertiesToInclude ) , null , '\t' ) ) ; } } else { this . clipboard = cloned ; } } , propertiesToInclude ) ; } return true ; } ; returns public paste = ( ) => { const { onAdd , propertiesToInclude , gridOption : { grid = 10 } , clipboard , isCut , } = this ; const padding = isCut ? 0 : grid ; if ( ! clipboard ) { return false ; } if ( typeof clipboard . cloneable !== 'undefined' && ! clipboard . cloneable ) { return false ; } this . isCut = false ; if ( clipboard . type === 'activeSelection' ) { if ( clipboard . getObjects ( ) . some ( ( obj ) => obj . superType === 'node' ) ) { this . canvas . discardActiveObject ( ) ; const objects = [ ] as any [ ] ; const linkObjects = [ ] as LinkOption [ ] ; clipboard . getObjects ( ) . forEach ( ( obj ) => { if ( typeof obj . cloneable !== 'undefined' && ! obj . cloneable ) { return ; } const clonedObj = obj . duplicate ( ) ; if ( clonedObj . type === 'SwitchNode' ) { clonedObj . set ( { left : obj . left + padding + padding , top : obj . top + padding , } ) ; } else { clonedObj . set ( { left : obj . left + padding , top : obj . top + padding , } ) ; } if ( obj . fromPort . length ) { obj . fromPort . forEach ( ( port ) => { if ( port . links . length ) { port . links . forEach ( ( link ) => { const linkTarget = { fromNode : clonedObj . id , fromPort : port . id , } as any ; const findIndex = clipboard . getObjects ( ) . findIndex ( ( compObj ) => compObj . id === link . toNode . id ) ; if ( findIndex >= 0 ) { linkTarget . toNodeIndex = findIndex ; linkObjects . push ( linkTarget ) ; } } ) ; } } ) ; } if ( clonedObj . dblclick ) { clonedObj . on ( 'mousedblclick' , this . eventHandler . object . mousedblclick ) ; } this . canvas . add ( clonedObj ) ; this . objects = this . getObjects ( ) ; this . portHandler . create ( clonedObj ) ; objects . push ( clonedObj ) ; } ) ; if ( linkObjects . length ) { linkObjects . forEach ( ( linkObject ) => { const { fromNode , fromPort , toNodeIndex } = linkObject ; const toNode = objects [ toNodeIndex ] ; const link = { type : 'curvedLink' , fromNodeId : fromNode , fromPortId : fromPort , toNodeId : toNode . id , toPortId : toNode . toPort . id , } ; this . linkHandler . create ( link ) ; } ) ; } const activeSelection = new fabric . ActiveSelection ( objects , { canvas : this . canvas , ... this . activeSelectionOption , } ) ; if ( isCut ) { this . clipboard = null ; } else { this . clipboard = activeSelection ; } if ( ! this . transactionHandler . active ) { this . transactionHandler . save ( 'paste' ) ; } this . canvas . setActiveObject ( activeSelection ) ; this . canvas . renderAll ( ) ; return true ; } } clipboard . clone ( ( clonedObj ) => { this . canvas . discardActiveObject ( ) ; clonedObj . set ( { left : clonedObj . left + padding , top : clonedObj . top + padding , id : isCut ? clipboard . id : uuid ( ) , evented : true , } ) ; if ( clonedObj . type === 'activeSelection' ) { clonedObj . canvas = this . canvas ; clonedObj . forEachObject ( ( obj ) => { obj . set ( 'id' , isCut ? obj . id : uuid ( ) ) ; this . canvas . add ( obj ) ; if ( obj . dblclick ) { obj . on ( 'mousedblclick' , this . eventHandler . object . mousedblclick ) ; } } ) ; } else { if ( clonedObj . superType === 'node' ) { clonedObj = clonedObj . duplicate ( ) ; } this . canvas . add ( clonedObj ) ; if ( clonedObj . dblclick ) { clonedObj . on ( 'mousedblclick' , this . eventHandler . object . mousedblclick ) ; } } const newClipboard = clipboard . set ( { top : clonedObj . top , left : clonedObj . left , } ) ; if ( isCut ) { this . clipboard = null ; } else { this . clipboard = newClipboard ; } if ( clonedObj . superType === 'node' ) { this . portHandler . create ( clonedObj ) ; } if ( ! this . transactionHandler . active ) { this . transactionHandler . save ( 'paste' ) ; } this . objects = this . getObjects ( ) ; if ( onAdd ) { onAdd ( clonedObj ) ; } clonedObj . setCoords ( ) ; this . canvas . setActiveObject ( clonedObj ) ; this . canvas . requestRenderAll ( ) ; } , propertiesToInclude ) ; return true ; } ; param { FabricImage } obj param { string } src public loadImage = ( obj , src ) => { let url = src ; if ( ! url ) { url = './images/sample/transparentBg.png' ; } fabric . util . loadImage ( url , source => { if ( obj . type !== 'image' ) { obj . set ( 'fill' , new fabric . Pattern ( { source , repeat : 'repeat' , } ) ) ; obj . setCoords ( ) ; this . canvas . renderAll ( ) ; return ; } obj . setElement ( source ) ; obj . setCoords ( ) ; this . canvas . renderAll ( ) ; } ) ; } ; param { FabricObject } obj public find = ( obj ) => this . findById ( obj . id ) ; param { string } id returns { ( FabricObject | null ) } public findById = ( id ) : FabricObject | null => { let findObject ; const exist = this . objects . some ( obj => { if ( obj . id === id ) { findObject = obj ; return true ; } return false ; } ) ; if ( ! exist ) { warning ( true , 'Not found object by id.' ) ; return null ; } return findObject ; } ; param { string } id returns public findOriginById = ( id ) => { let findObject ; const exist = this . objects . some ( obj => { if ( obj . id === id ) { findObject = obj ; return true ; } return false ; } ) ; if ( ! exist ) { console . warn ( 'Not found object by id.' ) ; return null ; } return findObject ; } ; param { string } id returns public findOriginByIdWithIndex = ( id ) => { let findObject ; let index = - 1 ; const exist = this . objects . some ( ( obj , i ) => { if ( obj . id === id ) { findObject = obj ; index = i ; return true ; } return false ; } ) ; if ( ! exist ) { console . warn ( 'Not found object by id.' ) ; return { } ; } return { object : findObject , index , } ; } ; param { FabricObject } obj param { boolean } find public select = ( obj , find ? ) => { let findObject = obj ; if ( find ) { findObject = this . find ( obj ) ; } if ( findObject ) { this . canvas . discardActiveObject ( ) ; this . canvas . setActiveObject ( findObject ) ; this . canvas . requestRenderAll ( ) ; } } ; param { string } id public selectById = ( id ) => { const findObject = this . findById ( id ) ; if ( findObject ) { this . canvas . discardActiveObject ( ) ; this . canvas . setActiveObject ( findObject ) ; this . canvas . requestRenderAll ( ) ; } } ; returns public selectAll = ( ) => { this . canvas . discardActiveObject ( ) ; const filteredObjects = this . canvas . getObjects ( ) . filter ( ( obj ) => { if ( obj . id === 'workarea' ) { return false ; } else if ( ! obj . evented ) { return false ; } else if ( obj . superType === 'link' ) { return false ; } else if ( obj . superType === 'port' ) { return false ; } else if ( obj . superType === 'element' ) { return false ; } else if ( obj . locked ) { return false ; } return true ; } ) ; if ( ! filteredObjects . length ) { return ; } if ( filteredObjects . length === 1 ) { this . canvas . setActiveObject ( filteredObjects [ 0 ] ) ; this . canvas . renderAll ( ) ; return ; } const activeSelection = new fabric . ActiveSelection ( filteredObjects , { canvas : this . canvas , ... this . activeSelectionOption , } ) ; this . canvas . setActiveObject ( activeSelection ) ; this . canvas . renderAll ( ) ; } ; param { FabricObject } obj param { number } width param { number } height public originScaleToResize = ( obj , width , height ) => { if ( obj . id === 'workarea' ) { this . setByPartial ( obj , { workareaWidth : obj . width , workareaHeight : obj . height , } ) ; } this . setByPartial ( obj , { scaleX : width / obj . width , scaleY : height / obj . height , } ) ; } ; param { number } width param { number } height public scaleToResize = ( width , height ) => { const activeObject = this . canvas . getActiveObject ( ) as FabricObject ; const { id } = activeObject ; const obj = { id , scaleX : width / activeObject . width , scaleY : height / activeObject . height , } ; this . setObject ( obj ) ; activeObject . setCoords ( ) ; this . canvas . requestRenderAll ( ) ; } ; param { * } json param { ( canvas : FabricCanvas ) => void } callback public importJSON = async ( json , callback ? ) => { if ( typeof json === 'string' ) { json = JSON . parse ( json ) ; } let prevLeft = 0 ; let prevTop = 0 ; this . canvas . setBackgroundColor ( this . canvasOption . backgroundColor , this . canvas . renderAll . bind ( this . canvas ) ) ; const workarea = json . find ( ( obj ) => obj . id === 'workarea' ) ; if ( ! this . workarea ) { this . workareaHandler . initialize ( ) ; } if ( workarea ) { prevLeft = workarea . left ; prevTop = workarea . top ; this . workarea . set ( workarea ) ; await this . workareaHandler . setImage ( workarea . src , true ) ; this . workarea . setCoords ( ) ; } else { this . canvas . centerObject ( this . workarea ) ; this . workarea . setCoords ( ) ; prevLeft = this . workarea . left ; prevTop = this . workarea . top ; } json . forEach ( ( obj ) => { if ( obj . id === 'workarea' ) { return ; } const canvasWidth = this . canvas . getWidth ( ) ; const canvasHeight = this . canvas . getHeight ( ) ; const { width , height , scaleX , scaleY , layout , left , top } = this . workarea ; if ( layout === 'fullscreen' ) { const leftRatio = canvasWidth / ( width * scaleX ) ; const topRatio = canvasHeight / ( height * scaleY ) ; obj . left *= leftRatio ; obj . top *= topRatio ; obj . scaleX *= leftRatio ; obj . scaleY *= topRatio ; } else { const diffLeft = left - prevLeft ; const diffTop = top - prevTop ; obj . left += diffLeft ; obj . top += diffTop ; } if ( obj . superType === 'element' ) { obj . id = uuid ( ) ; } this . add ( obj , false , true ) ; this . canvas . renderAll ( ) ; } ) ; this . objects = this . getObjects ( ) ; if ( callback ) { callback ( this . canvas ) ; } return Promise . resolve ( this . canvas ) ; } ; /**
     * Export json
     */ public exportJSON = ( ) => this . canvas . toObject ( this . propertiesToInclude ) . objects as FabricObject [ ] ; returns public toGroup = ( target ? ) => { const activeObject = target || ( this . canvas . getActiveObject ( ) as fabric . ActiveSelection ) ; if ( ! activeObject ) { return null ; } if ( activeObject . type !== 'activeSelection' ) { return null ; } const group = activeObject . toGroup ( ) as FabricObject < fabric . Group > ; group . set ( { id : uuid ( ) , name : 'New group' , type : 'group' , ... this . objectOption , } ) ; this . objects = this . getObjects ( ) ; if ( ! this . transactionHandler . active ) { this . transactionHandler . save ( 'group' ) ; } if ( this . onSelect ) { this . onSelect ( group ) ; } this . canvas . renderAll ( ) ; return group ; } ; returns public toActiveSelection = ( target ? ) => { const activeObject = target || ( this . canvas . getActiveObject ( ) as fabric . Group ) ; if ( ! activeObject ) { return ; } if ( activeObject . type !== 'group' ) { return ; } const activeSelection = activeObject . toActiveSelection ( ) ; this . objects = this . getObjects ( ) ; if ( ! this . transactionHandler . active ) { this . transactionHandler . save ( 'ungroup' ) ; } if ( this . onSelect ) { this . onSelect ( activeSelection ) ; } this . canvas . renderAll ( ) ; return activeSelection ; } ; /**
     * Bring forward
     */ public bringForward = ( ) => { const activeObject = this . canvas . getActiveObject ( ) as FabricObject ; if ( activeObject ) { this . canvas . bringForward ( activeObject ) ; if ( ! this . transactionHandler . active ) { this . transactionHandler . save ( 'bringForward' ) ; } const { onModified } = this ; if ( onModified ) { onModified ( activeObject ) ; } } } ; /**
     * Bring to front
     */ public bringToFront = ( ) => { const activeObject = this . canvas . getActiveObject ( ) as FabricObject ; if ( activeObject ) { this . canvas . bringToFront ( activeObject ) ; if ( ! this . transactionHandler . active ) { this . transactionHandler . save ( 'bringToFront' ) ; } const { onModified } = this ; if ( onModified ) { onModified ( activeObject ) ; } } } ; returns public sendBackwards = ( ) => { const activeObject = this . canvas . getActiveObject ( ) as FabricObject ; if ( activeObject ) { const firstObject = this . canvas . getObjects ( ) [ 1 ] as FabricObject ; if ( firstObject . id === activeObject . id ) { return ; } if ( ! this . transactionHandler . active ) { this . transactionHandler . save ( 'sendBackwards' ) ; } this . canvas . sendBackwards ( activeObject ) ; const { onModified } = this ; if ( onModified ) { onModified ( activeObject ) ; } } } ; /**
     * Send to back
     */ public sendToBack = ( ) => { const activeObject = this . canvas . getActiveObject ( ) as FabricObject ; if ( activeObject ) { this . canvas . sendToBack ( activeObject ) ; this . canvas . sendToBack ( this . canvas . getObjects ( ) [ 1 ] ) ; if ( ! this . transactionHandler . active ) { this . transactionHandler . save ( 'sendToBack' ) ; } const { onModified } = this ; if ( onModified ) { onModified ( activeObject ) ; } } } ; param { boolean } includeWorkarea public clear = ( includeWorkarea = false ) => { const ids = this . canvas . getObjects ( ) . reduce ( ( prev , curr ) => { if ( curr . superType === 'element' ) { prev . push ( curr . id ) ; return prev ; } return prev ; } , [ ] ) ; this . elementHandler . removeByIds ( ids ) ; if ( includeWorkarea ) { this . canvas . clear ( ) ; this . workarea = null ; } else { this . canvas . discardActiveObject ( ) ; this . canvas . getObjects ( ) . forEach ( ( obj ) => { if ( obj . id === 'grid' || obj . id === 'workarea' ) { return ; } this . canvas . remove ( obj ) ; } ) ; } this . objects = this . getObjects ( ) ; this . canvas . renderAll ( ) ; } ; /**
     * Start request animation frame
     */ public startRequestAnimFrame = ( ) => { if ( ! this . isRequsetAnimFrame ) { this . isRequsetAnimFrame = true ; const render = ( ) => { this . canvas . renderAll ( ) ; this . requestFrame = fabric . util . requestAnimFrame ( render ) ; } ; fabric . util . requestAnimFrame ( render ) ; } } ; /**
     * Stop request animation frame
     */ public stopRequestAnimFrame = ( ) => { this . isRequsetAnimFrame = false ; const cancelRequestAnimFrame = ( ( ) => window . cancelAnimationFrame || clearTimeout ) ( ) ; cancelRequestAnimFrame ( this . requestFrame ) ; } ; param { FabricObject } targetObject param { string } option public saveImage = ( targetObject , option = { name : 'New Image' , format : 'png' , quality : 1 } ) => { let dataUrl ; let target = targetObject ; if ( target ) { dataUrl = target . toDataURL ( option ) ; } else { target = this . canvas . getActiveObject ( ) as FabricObject ; if ( target ) { dataUrl = target . toDataURL ( option ) ; } } if ( dataUrl ) { const anchorEl = document . createElement ( 'a' ) ; anchorEl . href = dataUrl ; anchorEl . download = `${ option . name }.png` ; document . body . appendChild ( anchorEl ) ; anchorEl . click ( ) ; anchorEl . remove ( ) ; } } ; param { string } option public saveCanvasImage = ( option = { name : 'New Image' , format : 'png' , quality : 1 } ) => { const dataUrl = this . canvas . toDataURL ( option ) ; if ( dataUrl ) { const anchorEl = document . createElement ( 'a' ) ; anchorEl . href = dataUrl ; anchorEl . download = `${ option . name }.png` ; document . body . appendChild ( anchorEl ) ; anchorEl . click ( ) ; anchorEl . remove ( ) ; } } ; param { number } angle public rotate = ( angle ) => { const activeObject = this . canvas . getActiveObject ( ) ; if ( activeObject ) { this . set ( 'rotation' , angle ) ; activeObject . rotate ( angle ) ; this . canvas . requestRenderAll ( ) ; } } ; /**
     * Destroy canvas
     *
     */ public destroy = ( ) => { this . eventHandler . destroy ( ) ; this . guidelineHandler . destroy ( ) ; this . contextmenuHandler . destory ( ) ; this . tooltipHandler . destroy ( ) ; this . clear ( true ) ; } ; param { CanvasOption } canvasOption public setCanvasOption = ( canvasOption ) => { this . canvasOption = Object . assign ( { } , this . canvasOption , canvasOption ) ; this . canvas . setBackgroundColor ( canvasOption . backgroundColor , this . canvas . renderAll . bind ( this . canvas ) ) ; if ( typeof canvasOption . width !== 'undefined' && typeof canvasOption . height !== 'undefined' ) { if ( this . eventHandler ) { this . eventHandler . resize ( canvasOption . width , canvasOption . height ) ; } else { this . canvas . setWidth ( canvasOption . width ) . setHeight ( canvasOption . height ) ; } } if ( typeof canvasOption . selection !== 'undefined' ) { this . canvas . selection = canvasOption . selection ; } if ( typeof canvasOption . hoverCursor !== 'undefined' ) { this . canvas . hoverCursor = canvasOption . hoverCursor ; } if ( typeof canvasOption . defaultCursor !== 'undefined' ) { this . canvas . defaultCursor = canvasOption . defaultCursor ; } if ( typeof canvasOption . preserveObjectStacking !== 'undefined' ) { this . canvas . preserveObjectStacking = canvasOption . preserveObjectStacking ; } } ; param { KeyEvent } keyEvent public setKeyEvent = ( keyEvent ) => { this . keyEvent = Object . assign ( { } , this . keyEvent , keyEvent ) ; } ; param { FabricObjects } fabricObjects public setFabricObjects = ( fabricObjects ) => { this . fabricObjects = Object . assign ( { } , this . fabricObjects , fabricObjects ) ; } ; param { WorkareaOption } workareaOption public setWorkareaOption = ( workareaOption ) => { this . workareaOption = Object . assign ( { } , this . workareaOption , workareaOption ) ; if ( this . workarea ) { this . workarea . set ( { ... workareaOption , } ) ; } } ; param { GuidelineOption } guidelineOption public setGuidelineOption = ( guidelineOption ) => { this . guidelineOption = Object . assign ( { } , this . guidelineOption , guidelineOption ) ; if ( this . guidelineHandler ) { this . guidelineHandler . initialize ( ) ; } } ; param { GridOption } gridOption public setGridOption = ( gridOption ) => { this . gridOption = Object . assign ( { } , this . gridOption , gridOption ) ; } ; param { FabricObjectOption } objectOption public setObjectOption = ( objectOption ) => { this . objectOption = Object . assign ( { } , this . objectOption , objectOption ) ; } ; param { Partial < FabricObjectOption < fabric . ActiveSelection > > } activeSelectionOption public setActiveSelectionOption = ( activeSelectionOption ) => { this . activeSelectionOption = Object . assign ( { } , this . activeSelectionOption , activeSelectionOption ) ; } ; param { string [ ] } propertiesToInclude public setPropertiesToInclude = ( propertiesToInclude ) => { this . propertiesToInclude = union ( propertiesToInclude , this . propertiesToInclude ) ; } ; param { string } name param { typeof CustomHandler } handler public registerHandler = ( name , handler ) => { this . handlers [ name ] = new handler ( this ) ; return this . handlers [ name ] ; } ; } export default Handler ;