import { ImageCanvas } from "./designer/imagecanvas" ; import { Designer } from "./designer" ; import { NodeGraphicsItem , NodeGraphicsItemRenderState } from "./scene/nodegraphicsitem" ; import { ConnectionGraphicsItem } from "./scene/connectiongraphicsitem" ; import { SocketGraphicsItem , SocketType } from "./scene/socketgraphicsitem" ; import { GraphicsItem , MouseMoveEvent , MouseDownEvent , MouseUpEvent , MouseOverEvent } from "./scene/graphicsitem" ; import { SceneView } from "./scene/view" ; import { FrameGraphicsItem } from "./scene/framegraphicsitem" ; import { CommentGraphicsItem } from "./scene/commentgraphicsitem" ; import { NavigationGraphicsItem } from "./scene/navigationgraphicsitem" ; import { SelectionGraphicsItem } from "./scene/selectiongraphicsitem" ; import { Color } from "./designer/color" ; import { ItemClipboard } from "./clipboard" ; enum DragMode { None , Selection , Nodes , Socket , Frame , Pin , Comment } class Selection { nodes : NodeGraphicsItem [ ] ; public clear ( ) { this . nodes = [ ] ; } } class Rect { x : number ; y : number ; width : number ; height : number ; Rect ( x = 0 , y = 0 , width = 1 , height = 1 ) { this . x = x ; this . y = y ; this . width = width ; this . height = height ; } } export class NodeScene { canvas : HTMLCanvasElement ; context ! : CanvasRenderingContext2D ; contextExtra : any ; hasFocus : boolean ; frames : FrameGraphicsItem [ ] ; comments : CommentGraphicsItem [ ] ; navigations : NavigationGraphicsItem [ ] ; nodes : NodeGraphicsItem [ ] ; conns : ConnectionGraphicsItem [ ] ; selection : SelectionGraphicsItem ; dragMode : DragMode ; selectionRect : Rect ; draggedNode ? : NodeGraphicsItem ; hitSocket ? : SocketGraphicsItem ; hitConnection ? : ConnectionGraphicsItem ; selectedItems : GraphicsItem [ ] ; hitItem : GraphicsItem ; onconnectioncreated ? : ( item : ConnectionGraphicsItem ) => void ; onconnectiondestroyed ? : ( item : ConnectionGraphicsItem ) => void ; onnodeselected ? : ( item : NodeGraphicsItem ) => void ; oncommentselected ? : ( item : CommentGraphicsItem ) => void ; onframeselected ? : ( item : FrameGraphicsItem ) => void ; onnavigationselected ? : ( item : NavigationGraphicsItem ) => void ; onnodedeleted ? : ( item : NodeGraphicsItem ) => void ; onitemsdeleting ? : ( frames : FrameGraphicsItem [ ] , comments : CommentGraphicsItem [ ] , navs : NavigationGraphicsItem [ ] , cons : ConnectionGraphicsItem [ ] , nodes : NodeGraphicsItem [ ] ) => void ; onitemsdeleted ? : ( frames : FrameGraphicsItem [ ] , comments : CommentGraphicsItem [ ] , navs : NavigationGraphicsItem [ ] , cons : ConnectionGraphicsItem [ ] , nodes : NodeGraphicsItem [ ] ) => void ; oncopy ? : ( evt : ClipboardEvent ) => void ; oncut ? : ( evt : ClipboardEvent ) => void ; onpaste ? : ( evt : ClipboardEvent ) => void ; onlibrarymenu ? : ( ) => void ; view : SceneView ; _mouseMove : ( evt : MouseEvent ) => void ; _mouseDown : ( evt : MouseEvent ) => void ; _mouseUp : ( evt : MouseEvent ) => void ; _mouseClick : ( evt : MouseEvent ) => void ; _keyDown : ( evt : KeyboardEvent ) => void ; _contextMenu : ( evt : MouseEvent ) => void ; _copyEvent : ( evt : ClipboardEvent ) => void ; _cutEvent : ( evt : ClipboardEvent ) => void ; _pasteEvent : ( evt : ClipboardEvent ) => void ; copyElement : HTMLInputElement ; constructor ( canvas ) { this . canvas = canvas ; this . context = this . canvas . getContext ( "2d" ) ; this . view = new SceneView ( canvas ) ; this . hasFocus = false ; this . contextExtra = this . context ; this . frames = [ ] ; this . comments = [ ] ; this . nodes = [ ] ; this . conns = [ ] ; this . navigations = [ ] ; this . dragMode = null ; this . selectionRect = new Rect ( ) ; this . selectedItems = [ ] ; this . hitItem = null ; const self = this ; this . _mouseMove = function ( evt ) { self . onMouseMove ( evt ) ; } ; canvas . addEventListener ( "mousemove" , this . _mouseMove ) ; self . _mouseDown = function ( evt ) { self . onMouseDown ( evt ) ; } ; canvas . addEventListener ( "mousedown" , self . _mouseDown ) ; self . _mouseUp = function ( evt ) { self . onMouseUp ( evt ) ; if ( evt . target == canvas ) { self . hasFocus = true ; self . copyElement . focus ( ) ; self . copyElement . select ( ) ; } else { self . hasFocus = false ; } } ; canvas . addEventListener ( "mouseup" , self . _mouseUp ) ; self . _keyDown = function ( evt ) { if ( evt . key == "Delete" && self . hasFocus && self . selectedItems . length != 0 ) { self . deleteItems ( self . selectedItems ) ; } if ( evt . key == " " && self . view . isMouseOverCanvas ( ) ) { if ( self . onlibrarymenu != null && self . hitItem == null ) { self . onlibrarymenu ( ) ; } } } ; window . addEventListener ( "keydown" , self . _keyDown , true ) ; self . _contextMenu = function ( evt ) { evt . preventDefault ( ) ; } ; canvas . addEventListener ( "contextmenu" , self . _contextMenu ) ; this . _copyEvent = evt => { if ( self . hasFocus && evt . target == self . copyElement ) { evt . preventDefault ( ) ; self . onCopy ( evt ) ; } } ; document . addEventListener ( "copy" , this . _copyEvent ) ; this . _cutEvent = evt => { if ( self . hasFocus && evt . target == self . copyElement ) { evt . preventDefault ( ) ; self . onCut ( evt ) ; self . deleteItems ( this . selectedItems ) ; } } ; document . addEventListener ( "cut" , this . _cutEvent ) ; this . _pasteEvent = evt => { if ( self . hasFocus && evt . target == self . copyElement ) { evt . preventDefault ( ) ; self . copyElement . value = " " ; self . onPaste ( evt ) ; } } ; document . addEventListener ( "paste" , this . _pasteEvent ) ; this . copyElement = document . createElement ( "input" ) ; self . copyElement . value = " " ; self . copyElement . style . opacity = "0" ; self . copyElement . style . width = "1px" ; self . copyElement . style . height = "1px" ; document . body . appendChild ( this . copyElement ) ; } dispose ( ) { this . canvas . removeEventListener ( "mousemove" , this . _mouseMove ) ; this . canvas . removeEventListener ( "mousedown" , this . _mouseDown ) ; this . canvas . removeEventListener ( "mouseup" , this . _mouseUp ) ; window . removeEventListener ( "click" , this . _mouseClick ) ; window . removeEventListener ( "keydown" , this . _keyDown , true ) ; this . canvas . removeEventListener ( "contextmenu" , this . _contextMenu ) ; document . removeEventListener ( "copy" , this . _copyEvent ) ; document . removeEventListener ( "paste" , this . _pasteEvent ) ; this . canvas = null ; this . context = null ; this . view = null ; this . contextExtra = null ; this . frames = [ ] ; this . comments = [ ] ; this . nodes = [ ] ; this . conns = [ ] ; this . navigations = [ ] ; this . selectionRect = null ; this . selectedItems = [ ] ; this . hitItem = null ; } setSelectedItems ( items , createSelection = false ) { this . selectedItems = items ; if ( createSelection == true ) { const sel = new SelectionGraphicsItem ( this , this . view ) ; sel . setHitItems ( items ) ; this . selection = sel ; } } addNode ( item ) { this . nodes . push ( item ) ; item . setScene ( this ) ; } addComment ( item ) { item . setScene ( this ) ; this . comments . push ( item ) ; } removeComment ( item ) { const i = this . comments . indexOf ( item ) ; if ( i !== - 1 ) this . comments . splice ( i , 1 ) ; } addFrame ( item ) { item . setScene ( this ) ; this . frames . push ( item ) ; } removeFrame ( item ) { const i = this . frames . indexOf ( item ) ; if ( i !== - 1 ) this . frames . splice ( i , 1 ) ; } addNavigation ( nav ) { nav . setScene ( this ) ; this . navigations . push ( nav ) ; } removeNavigation ( item ) { const i = this . navigations . indexOf ( item ) ; if ( i !== - 1 ) this . navigations . splice ( i , 1 ) ; } deleteNode ( item ) { const conns = this . conns ; for ( let i = this . conns . length - 1 ; i >= 0 ; i -- ) { const con = this . conns [ i ] ; if ( ( con . socketA && con . socketA . node . id == item . id ) || ( con . socketB && con . socketB . node . id == item . id ) ) { this . removeConnection ( con ) ; } } this . nodes . splice ( this . nodes . indexOf ( item ) , 1 ) ; if ( this . onnodeselected ) this . onnodeselected ( null ) ; if ( this . onnodedeleted ) this . onnodedeleted ( item ) ; } deleteItems ( items ) { const frames = [ ] ; const comments = [ ] ; const navs = [ ] ; const cons = [ ] ; const nodes = [ ] ; for ( const item of items ) { if ( item instanceof FrameGraphicsItem ) { frames . push ( < FrameGraphicsItem > item ) ; } if ( item instanceof CommentGraphicsItem ) { comments . push ( < CommentGraphicsItem > item ) ; } if ( item instanceof NavigationGraphicsItem ) { navs . push ( < NavigationGraphicsItem > item ) ; } if ( item instanceof NodeGraphicsItem ) { nodes . push ( < NodeGraphicsItem > item ) ; } } if ( frames . length == 0 && comments . length == 0 && navs . length == 0 && nodes . length == 0 ) return ; const conDict = new Map < string , ConnectionGraphicsItem > ( ) ; for ( const node of nodes ) { for ( const sock of node . sockets ) { for ( const con of sock . conns ) { conDict . set ( con . id , con ) ; } } } for ( const [ key , con ] of conDict ) cons . push ( con ) ; if ( this . onitemsdeleting ) { this . onitemsdeleting ( frames , comments , navs , cons , nodes ) ; } for ( const frame of frames ) this . removeFrame ( frame ) ; for ( const comment of comments ) this . removeComment ( comment ) ; for ( const nav of navs ) this . removeNavigation ( nav ) ; for ( const node of nodes ) this . deleteNode ( node ) ; if ( this . onitemsdeleted ) { this . onitemsdeleted ( frames , comments , navs , cons , nodes ) ; } } getNodeById ( id ) { for ( const node of this . nodes ) { if ( node . id == id ) return node ; } return null ; } param leftNode param rightNode returns remainsDAG ( leftNode , rightNode ) { let checked = new Set ( ) ; checked . add ( leftNode ) ; let expanding = [ rightNode ] ; while ( expanding . length > 0 ) { let nodeA = expanding . pop ( ) ; for ( let socket of nodeA . getOutSockets ( ) ) { for ( let conn of socket . conns ) { let nodeB = conn . socketB . node ; if ( nodeB === leftNode ) { return false ; } else { expanding . push ( nodeB ) ; checked . add ( nodeA ) ; } } } } return true ; } addConnection ( con ) { this . conns . push ( con ) ; con . socketA . addConnection ( con ) ; con . socketB . addConnection ( con ) ; if ( this . onconnectioncreated ) this . onconnectioncreated ( con ) ; } createConnection ( leftId , rightId , rightIndex = 0 ) { const con = new ConnectionGraphicsItem ( ) ; const leftNode = this . getNodeById ( leftId ) ; const rightNode = this . getNodeById ( rightId ) ; con . socketA = leftNode . sockets . find ( x => x . socketType == SocketType . Out ) ; con . socketB = rightNode . sockets [ rightIndex ] ; this . addConnection ( con ) ; } removeConnection ( con ) { this . conns . splice ( this . conns . indexOf ( con ) , 1 ) ; con . socketA . removeConnection ( con ) ; con . socketB . removeConnection ( con ) ; if ( this . onconnectiondestroyed ) this . onconnectiondestroyed ( con ) ; } drawActiveConnection ( ) { const mouse = this . view . getMouseSceneSpace ( ) ; const mouseX = mouse . x ; const mouseY = mouse . y ; const ctx = this . context ; if ( this . hitSocket ) { ctx . beginPath ( ) ; ctx . strokeStyle = "rgb(200, 200, 200)" ; ctx . lineWidth = 4 ; ctx . moveTo ( this . hitSocket . centerX ( ) , this . hitSocket . centerY ( ) ) ; if ( this . hitSocket . socketType == SocketType . Out ) { ctx . bezierCurveTo ( this . hitSocket . centerX ( ) + 60 , this . hitSocket . centerY ( ) , mouseX - 60 , mouseY , mouseX , mouseY ) ; } else { ctx . bezierCurveTo ( this . hitSocket . centerX ( ) - 60 , this . hitSocket . centerY ( ) , mouseX + 60 , mouseY , mouseX , mouseY ) ; } ctx . setLineDash ( [ 5 , 3 ] ) ; ctx . stroke ( ) ; ctx . setLineDash ( [ ] ) ; ctx . beginPath ( ) ; ctx . fillStyle = "rgb(200, 200, 200)" ; const radius = 6 ; ctx . arc ( mouseX , mouseY , radius , 0 , 2 * Math . PI ) ; ctx . fill ( ) ; } } clearAndDrawGrid ( ) { this . view . clear ( this . context , "#4A5050" ) ; this . view . setViewMatrix ( this . context ) ; this . view . drawGrid ( this . context , 33.33333 , "#4E5454" , 1 ) ; this . view . drawGrid ( this . context , 100 , "#464C4C" , 3 ) ; } draw ( ) { this . clearAndDrawGrid ( ) ; for ( const frame of this . frames ) frame . draw ( this . context ) ; for ( const comment of this . comments ) comment . draw ( this . context ) ; for ( const con of this . conns ) { if ( con == this . hitConnection ) continue ; con . draw ( this . context ) ; } if ( this . hitSocket ) { this . drawActiveConnection ( ) ; } const mouse = this . view . getMouseSceneSpace ( ) ; const mouseX = mouse . x ; const mouseY = mouse . y ; const nodeState = { hovered : false , selected : false } ; for ( const item of this . nodes ) { if ( item . isPointInside ( mouseX , mouseY ) && this . hitSocket == null ) nodeState . hovered = true ; else nodeState . hovered = false ; item . draw ( this . context , nodeState ) ; } for ( const nav of this . navigations ) nav . draw ( this . context ) ; if ( this . selection ) this . selection . draw ( this . context ) ; if ( this . selectedItems . length > 0 ) { this . drawSelectedItems ( this . selectedItems , this . context ) ; } } drawSelectedItems ( items , ctx ) { for ( const item of items ) { ctx . beginPath ( ) ; ctx . lineWidth = 3 ; ctx . strokeStyle = "rgba(255, 255, 255)" ; const rect = item . getRect ( ) ; rect . expand ( 15 ) ; ctx . rect ( rect . left , rect . top , rect . width , rect . height ) ; ctx . stroke ( ) ; ctx . fillStyle = "rgba(255, 255, 255, 0.1)" ; ctx . rect ( rect . left , rect . top , rect . width , rect . height ) ; ctx . fill ( ) ; } } onCopy ( evt ) { if ( this . oncopy ) this . oncopy ( evt ) ; } onCut ( evt ) { if ( this . oncut ) this . oncut ( evt ) ; } onPaste ( evt ) { if ( this . onpaste ) this . onpaste ( evt ) ; } onMouseDown ( evt ) { const pos = this . getScenePos ( evt ) ; const mouseX = pos . x ; const mouseY = pos . y ; if ( evt . button == 0 ) { const hitItem = this . getHitItem ( mouseX , mouseY ) ; const mouseEvent = new MouseDownEvent ( ) ; mouseEvent . globalX = pos . x ; mouseEvent . globalY = pos . y ; mouseEvent . shiftKey = evt . shiftKey ; mouseEvent . altKey = evt . altKey ; mouseEvent . ctrlKey = evt . ctrlKey ; if ( hitItem != null ) { mouseEvent . localX = hitItem . left - pos . x ; mouseEvent . localY = hitItem . top - pos . y ; hitItem . mouseDown ( mouseEvent ) ; if ( mouseEvent . isAccepted ) { this . hitItem = hitItem ; if ( hitItem instanceof NodeGraphicsItem ) { const hitNode = < NodeGraphicsItem > hitItem ; this . moveNodeToTop ( hitNode ) ; if ( this . onnodeselected ) { if ( hitNode ) this . onnodeselected ( hitNode ) ; else this . onnodeselected ( hitNode ) ; } } if ( hitItem instanceof CommentGraphicsItem ) { const hitComment = < CommentGraphicsItem > hitItem ; if ( this . oncommentselected ) { if ( hitComment ) this . oncommentselected ( hitComment ) ; else this . oncommentselected ( hitComment ) ; } } if ( hitItem instanceof FrameGraphicsItem ) { const hit = < FrameGraphicsItem > hitItem ; if ( this . onframeselected ) { if ( hit ) this . onframeselected ( hit ) ; else this . onframeselected ( hit ) ; } } if ( hitItem instanceof NavigationGraphicsItem ) { const hit = < NavigationGraphicsItem > hitItem ; if ( this . onnavigationselected ) { if ( hit ) this . onnavigationselected ( hit ) ; else this . onnavigationselected ( hit ) ; } } if ( ! ( hitItem instanceof SelectionGraphicsItem ) && ! ( hitItem instanceof SocketGraphicsItem ) ) this . selectedItems = [ hitItem ] ; } } else { const hitItem = new SelectionGraphicsItem ( this , this . view ) ; mouseEvent . localX = hitItem . left - pos . x ; mouseEvent . localY = hitItem . top - pos . y ; hitItem . mouseDown ( mouseEvent ) ; this . selection = hitItem ; this . hitItem = hitItem ; } } } moveNodeToTop ( node ) { const index = this . nodes . indexOf ( node ) ; if ( index === - 1 ) { console . log ( "Attempting to push node that doesnt exist in node list" ) ; } this . nodes . splice ( index , 1 ) ; this . nodes . push ( node ) ; } onMouseUp ( evt ) { const pos = this . getScenePos ( evt ) ; const mouseX = pos . x ; const mouseY = pos . y ; if ( evt . button == 0 ) { if ( this . hitItem != null ) { const hitItem = this . hitItem ; const mouseEvent = new MouseUpEvent ( ) ; mouseEvent . globalX = pos . x ; mouseEvent . globalY = pos . y ; mouseEvent . localX = hitItem . left - pos . x ; mouseEvent . localY = hitItem . top - pos . y ; mouseEvent . shiftKey = evt . shiftKey ; mouseEvent . altKey = evt . altKey ; mouseEvent . ctrlKey = evt . ctrlKey ; hitItem . mouseUp ( mouseEvent ) ; this . hitItem = null ; } } } onMouseMove ( evt ) { const pos = this . getScenePos ( evt ) ; if ( this . hitItem ) { const mouseEvent = new MouseMoveEvent ( ) ; mouseEvent . globalX = pos . x ; mouseEvent . globalY = pos . y ; mouseEvent . localX = this . hitItem . left - pos . x ; mouseEvent . localY = this . hitItem . top - pos . y ; mouseEvent . shiftKey = evt . shiftKey ; mouseEvent . altKey = evt . altKey ; mouseEvent . ctrlKey = evt . ctrlKey ; const drag = this . view . getMouseDeltaSceneSpace ( ) ; mouseEvent . deltaX = drag . x ; mouseEvent . deltaY = drag . y ; this . hitItem . mouseMove ( mouseEvent ) ; } else { const hitItem = this . getHitItem ( pos . x , pos . y ) ; if ( hitItem ) { const mouseEvent = new MouseOverEvent ( ) ; mouseEvent . globalX = pos . x ; mouseEvent . globalY = pos . y ; mouseEvent . localX = hitItem . left - pos . x ; mouseEvent . localY = hitItem . top - pos . y ; mouseEvent . shiftKey = evt . shiftKey ; mouseEvent . altKey = evt . altKey ; mouseEvent . ctrlKey = evt . ctrlKey ; hitItem . mouseOver ( mouseEvent ) ; } else { this . view . canvas . style . cursor = "default" ; } } } getHitNode ( x , y ) { for ( let index = this . nodes . length - 1 ; index >= 0 ; index -- ) { const node = this . nodes [ index ] ; if ( node . isPointInside ( x , y ) ) return node ; } return null ; } getHitSocket ( x , y ) { for ( const node of this . nodes ) { for ( const sock of node . sockets ) { if ( sock . isPointInside ( x , y ) ) return sock ; } } return null ; } getHitItem ( x , y ) { const hitItem = this . _getHitItem ( x , y ) ; if ( hitItem != null && this . isItemSelected ( hitItem ) && this . selection != null ) { if ( this . selection . isPointInside ( x , y ) ) return this . selection ; } return hitItem ; } _getHitItem ( x , y ) { for ( let index = this . navigations . length - 1 ; index >= 0 ; index -- ) { const nav = this . navigations [ index ] ; if ( nav . isPointInside ( x , y ) ) return nav ; } for ( let index = this . nodes . length - 1 ; index >= 0 ; index -- ) { const node = this . nodes [ index ] ; for ( const sock of node . sockets ) { if ( sock . isPointInside ( x , y ) ) return sock ; } if ( node . isPointInside ( x , y ) ) return node ; } for ( let index = this . comments . length - 1 ; index >= 0 ; index -- ) { const comment = this . comments [ index ] ; if ( comment . isPointInside ( x , y ) ) return comment ; } for ( let index = this . frames . length - 1 ; index >= 0 ; index -- ) { const frame = this . frames [ index ] ; if ( frame . isPointInside ( x , y ) ) return frame ; } return null ; } isItemSelected ( hitItem ) { for ( const item of this . selectedItems ) if ( item == hitItem ) return true ; return false ; } getScenePos ( evt ) { const canvasPos = _getMousePos ( this . canvas , evt ) ; return this . view . canvasToSceneXY ( canvasPos . x , canvasPos . y ) ; } save ( ) { const data = { } ; const nodes = { } ; for ( const node of this . nodes ) { const n = { } ; n [ "id" ] = node . id ; n [ "x" ] = node . centerX ( ) ; n [ "y" ] = node . centerY ( ) ; nodes [ node . id ] = n ; } data [ "nodes" ] = nodes ; const frames = [ ] ; for ( const frame of this . frames ) { const n = { } ; n [ "x" ] = frame . left ; n [ "y" ] = frame . top ; n [ "width" ] = frame . getWidth ( ) ; n [ "height" ] = frame . getHeight ( ) ; n [ "title" ] = frame . title ; n [ "showTitle" ] = frame . showTitle ; n [ "description" ] = frame . description ; n [ "color" ] = frame . color . toHex ( ) ; frames . push ( n ) ; } data [ "frames" ] = frames ; const comments = [ ] ; for ( const comment of this . comments ) { const n = { } ; n [ "x" ] = comment . left ; n [ "y" ] = comment . top ; n [ "text" ] = comment . text ; n [ "color" ] = comment . color . toHex ( ) ; comments . push ( n ) ; } data [ "comments" ] = comments ; const navs = [ ] ; for ( const nav of this . navigations ) { const n = { } ; n [ "x" ] = nav . left ; n [ "y" ] = nav . top ; navs . push ( n ) ; } data [ "navigations" ] = navs ; return data ; } static load ( designer , data , canvas ) { const s = new NodeScene ( canvas ) ; for ( const dNode of designer . nodes ) { const node = new NodeGraphicsItem ( dNode . title ) ; for ( const input of dNode . getInputs ( ) ) { node . addSocket ( input , input , SocketType . In ) ; } node . addSocket ( "output" , "output" , SocketType . Out ) ; s . addNode ( node ) ; node . id = dNode . id ; const x = data [ "nodes" ] [ node . id ] . x ; const y = data [ "nodes" ] [ node . id ] . y ; node . setCenter ( x , y ) ; } for ( const dcon of designer . conns ) { const con = new ConnectionGraphicsItem ( ) ; con . id = dcon . id ; const leftNode = s . getNodeById ( dcon . leftNode . id ) ; const rightNode = s . getNodeById ( dcon . rightNode . id ) ; con . socketA = leftNode . getOutSocketByName ( dcon . leftNodeOutput ) ; con . socketB = rightNode . getInSocketByName ( dcon . rightNodeInput ) ; s . addConnection ( con ) ; } if ( data . frames ) { for ( const d of data . frames ) { const frame = new FrameGraphicsItem ( s . view ) ; frame . setPos ( d . x , d . y ) ; frame . setSize ( d . width , d . height ) ; frame . setTitle ( d . title ) ; frame . setShowTitle ( d . showTitle ) ; frame . setDescription ( d . description ) ; frame . color = Color . parse ( d . color ) ; s . addFrame ( frame ) ; } } if ( data . comments ) { for ( const d of data . comments ) { const comment = new CommentGraphicsItem ( s . view ) ; comment . setPos ( d . x , d . y ) ; comment . setText ( d . text ) ; comment . color = Color . parse ( d . color ) ; s . addComment ( comment ) ; } } if ( data . navigations ) { for ( const d of data . navigations ) { const nav = new NavigationGraphicsItem ( ) ; nav . setPos ( d . x , d . y ) ; s . addNavigation ( nav ) ; } } return s ; } } function _getMousePos ( canvas , evt ) { const rect = canvas . getBoundingClientRect ( ) ; return { x : evt . clientX - rect . left , y : evt . clientY - rect . top } ; }