UNPKG

@niuee/board

Version:

<h1 align="center"> board </h1> <p align="center"> board supercharges your html canvas element giving it the capabilities to pan, zoom, rotate, and much more. </p> <p align="center"> <a href="https://www.npmjs.com/package/@niuee/board">

3 lines (2 loc) 14.1 kB
import{PointCal}from"point2point";class TemplateStateMachine{constructor(states,initialState,context){this._states=states,this._currentState=initialState,this._context=context,this._statesArray=Object.keys(states),this._stateChangeCallbacks=[],this._happensCallbacks=[]}switchTo(state){this._currentState=state}happens(event,payload){this._happensCallbacks.forEach((callback=>callback(event,payload,this._context)));const nextState=this._states[this._currentState].handles(event,payload,this._context);if(void 0!==nextState&&nextState!==this._currentState){const originalState=this._currentState;this._states[this._currentState].uponLeave(this._context),this.switchTo(nextState),this._states[this._currentState].uponEnter(this._context),this._stateChangeCallbacks.forEach((callback=>callback(originalState,this._currentState)))}return nextState}onStateChange(callback){this._stateChangeCallbacks.push(callback)}onHappens(callback){this._happensCallbacks.push(callback)}get currentState(){return this._currentState}setContext(context){this._context=context}get possibleStates(){return this._statesArray}get states(){return this._states}}class TemplateState{constructor(){this._guards={},this._eventGuards={}}get guards(){return this._guards}get eventGuards(){return this._eventGuards}uponEnter(context){}uponLeave(context){}handles(event,payload,context){if(this.eventReactions[event]){this.eventReactions[event].action(context,payload);const targetState=this.eventReactions[event].defaultTargetState,guardToEvaluate=this._eventGuards[event];if(guardToEvaluate){const target=guardToEvaluate.find((guard=>!!this.guards[guard.guard]&&this.guards[guard.guard](context)));return target?target.target:targetState}return targetState}}}function convertFromWindow2ViewPort(point,canvas){const canvasBoundingRect=canvas.getBoundingClientRect(),cameraCenterInWindow={x:canvasBoundingRect.left+(canvasBoundingRect.right-canvasBoundingRect.left)/2,y:canvasBoundingRect.top+(canvasBoundingRect.bottom-canvasBoundingRect.top)/2};return PointCal.subVector(point,cameraCenterInWindow)}class KmtIdleState extends TemplateState{constructor(){super(),this._guards={isIdle:()=>!0},this._eventGuards={},this._eventReactions={spacebarDown:{action:this.spacebarDownHandler,defaultTargetState:"READY_TO_PAN_VIA_SPACEBAR"},scroll:{action:this.scrollHandler,defaultTargetState:"IDLE"},scrollWithCtrl:{action:this.scrollWithCtrlHandler,defaultTargetState:"IDLE"},middlePointerDown:{action:this.middlePointerDownHandler,defaultTargetState:"READY_TO_PAN_VIA_SCROLL_WHEEL"}}}get eventReactions(){return this._eventReactions}scrollHandler(context,payload){const delta=Object.assign({},payload);context.alignCoordinateSystem||(delta.deltaY=-delta.deltaY),context.notifyOnPan({x:delta.deltaX,y:delta.deltaY})}scrollWithCtrlHandler(context,payload){let scrollSensitivity=.005;Math.abs(payload.deltaY)>100&&(scrollSensitivity=5e-4);const zoomAmount=payload.deltaY*scrollSensitivity,anchorPoint=convertFromWindow2ViewPort({x:payload.x,y:payload.y},context.canvas);context.alignCoordinateSystem||(anchorPoint.y=-anchorPoint.y),context.notifyOnZoom(-5*zoomAmount,anchorPoint)}spacebarDownHandler(context,payload){context.canvas.style.cursor="grab"}middlePointerDownHandler(context,payload){context.setInitialCursorPosition({x:payload.x,y:payload.y}),context.canvas.style.cursor="grabbing"}}class ReadyToSelectState extends TemplateState{constructor(){super(),this.leftPointerMove=((context,payload)=>{const viewportPoint=convertFromWindow2ViewPort({x:payload.x,y:payload.y},context.canvas);context.setSelectionEndPoint(viewportPoint),context.toggleSelectionBox(!0)}).bind(this),this._eventReactions={leftPointerUp:{action:()=>"IDLE",defaultTargetState:"IDLE"},leftPointerMove:{action:this.leftPointerMove,defaultTargetState:"SELECTING"}}}get eventReactions(){return this._eventReactions}}class ReadyToPanViaSpaceBarState extends TemplateState{constructor(){super(),this._eventReactions={spacebarUp:{action:this.spacebarUpHandler,defaultTargetState:"IDLE"},leftPointerDown:{action:this.leftPointerDownHandler,defaultTargetState:"INITIAL_PAN"}}}get eventReactions(){return this._eventReactions}leftPointerDownHandler(context,payload){context.setInitialCursorPosition({x:payload.x,y:payload.y}),context.canvas.style.cursor="grabbing"}spacebarUpHandler(context,payload){context.canvas.style.cursor="default"}}class InitialPanState extends TemplateState{constructor(){super(),this._eventReactions={leftPointerUp:{action:this.leftPointerUpHandler,defaultTargetState:"READY_TO_PAN_VIA_SPACEBAR"},leftPointerMove:{action:this.leftPointerMoveHandler,defaultTargetState:"PAN"},spacebarUp:{action:()=>"IDLE",defaultTargetState:"IDLE"},leftPointerDown:{action:()=>"PAN",defaultTargetState:"PAN"}}}get eventReactions(){return this._eventReactions}leftPointerMoveHandler(context,payload){const delta={x:context.initialCursorPosition.x-payload.x,y:context.initialCursorPosition.y-payload.y};context.alignCoordinateSystem||(delta.y=-delta.y),context.notifyOnPan(delta),context.setInitialCursorPosition({x:payload.x,y:payload.y})}leftPointerUpHandler(context,payload){context.canvas.style.cursor="grab"}}class ReadyToPanViaScrollWheelState extends TemplateState{constructor(){super(),this._eventReactions={middlePointerUp:{action:this.middlePointerUpHandler,defaultTargetState:"IDLE"},middlePointerMove:{action:this.middlePointerMoveHandler,defaultTargetState:"PAN_VIA_SCROLL_WHEEL"}}}get eventReactions(){return this._eventReactions}middlePointerMoveHandler(context,payload){context.canvas.style.cursor="grabbing"}middlePointerUpHandler(context,payload){context.canvas.style.cursor="default"}}class PanState extends TemplateState{constructor(){super(),this._eventReactions={leftPointerUp:{action:this.leftPointerUpHandler,defaultTargetState:"READY_TO_PAN_VIA_SPACEBAR"},leftPointerMove:{action:this.leftPointerMoveHandler,defaultTargetState:"PAN"},spacebarUp:{action:this.spacebarUpHandler,defaultTargetState:"IDLE"}}}get eventReactions(){return this._eventReactions}leftPointerMoveHandler(context,payload){const delta={x:context.initialCursorPosition.x-payload.x,y:context.initialCursorPosition.y-payload.y};context.alignCoordinateSystem||(delta.y=-delta.y),context.notifyOnPan(delta),context.setInitialCursorPosition({x:payload.x,y:payload.y})}spacebarUpHandler(context,payload){context.canvas.style.cursor="default"}leftPointerUpHandler(context,payload){context.canvas.style.cursor="grab"}}class PanViaScrollWheelState extends TemplateState{constructor(){super(...arguments),this._eventReactions={middlePointerUp:{action:this.middlePointerUpHandler,defaultTargetState:"IDLE"},middlePointerMove:{action:this.middlePointerMoveHandler,defaultTargetState:"PAN_VIA_SCROLL_WHEEL"}}}get eventReactions(){return this._eventReactions}middlePointerMoveHandler(context,payload){const delta={x:context.initialCursorPosition.x-payload.x,y:context.initialCursorPosition.y-payload.y};context.alignCoordinateSystem||(delta.y=-delta.y),context.notifyOnPan(delta),context.setInitialCursorPosition({x:payload.x,y:payload.y})}middlePointerUpHandler(context,payload){context.canvas.style.cursor="default"}}function createKmtInputStateMachine(context){const states={IDLE:new KmtIdleState,READY_TO_PAN_VIA_SPACEBAR:new ReadyToPanViaSpaceBarState,INITIAL_PAN:new InitialPanState,PAN:new PanState,READY_TO_PAN_VIA_SCROLL_WHEEL:new ReadyToPanViaScrollWheelState,PAN_VIA_SCROLL_WHEEL:new PanViaScrollWheelState};return new TemplateStateMachine(states,"IDLE",context)}class IdleState extends TemplateState{constructor(){super(...arguments),this._eventReactions={touchstart:{action:this.touchstart,defaultTargetState:"IDLE"},touchend:{action:this.touchend,defaultTargetState:"IDLE"}},this._guards={touchPointsCount:(context=>2===context.getCurrentTouchPointsCount()).bind(this)},this._eventGuards={touchstart:[{guard:"touchPointsCount",target:"PENDING"}],touchend:[{guard:"touchPointsCount",target:"PENDING"}]}}get eventReactions(){return this._eventReactions}touchstart(context,payload){context.addTouchPoints(payload.points)}touchend(context,payload){context.removeTouchPoints(payload.points.map((p=>p.ident)))}}class PendingState extends TemplateState{constructor(){super(...arguments),this._eventReactions={touchstart:{action:this.touchstart,defaultTargetState:"IDLE"},touchend:{action:this.touchend,defaultTargetState:"IDLE"},touchmove:{action:this.touchmove,defaultTargetState:"IN_PROGRESS"}}}get eventReactions(){return this._eventReactions}touchstart(context,payload){context.addTouchPoints(payload.points)}touchend(context,payload){context.removeTouchPoints(payload.points.map((p=>p.ident)))}touchmove(context,payload){const idents=payload.points.map((p=>p.ident)),initialPositions=context.getInitialTouchPointsPositions(idents),currentPositions=payload.points,initialStartAndEndDistance=PointCal.distanceBetweenPoints(initialPositions[0],initialPositions[1]),currentStartAndEndDistance=PointCal.distanceBetweenPoints(currentPositions[0],currentPositions[1]),midPoint=PointCal.linearInterpolation(initialPositions[0],initialPositions[1],.5),currentMidPoint=PointCal.linearInterpolation(currentPositions[0],currentPositions[1],.5),midPointDelta=PointCal.subVector(midPoint,currentMidPoint),boundingRect=context.canvas.getBoundingClientRect(),cameraCenterInWindow={x:boundingRect.left+boundingRect.width/2,y:boundingRect.top+boundingRect.height/2},midPointInViewPort=PointCal.subVector(midPoint,cameraCenterInWindow);let panZoom=Math.abs(currentStartAndEndDistance-initialStartAndEndDistance)>PointCal.distanceBetweenPoints(midPoint,currentMidPoint)?"ZOOMING":"PANNING";switch(context.updateTouchPoints(currentPositions),panZoom){case"ZOOMING":context.notifyOnZoom(.005*(currentStartAndEndDistance-initialStartAndEndDistance),midPointInViewPort);break;case"PANNING":context.notifyOnPan(midPointDelta);break;default:console.warn("Unknown panZoom state",panZoom)}}}class InProgressState extends TemplateState{constructor(){super(...arguments),this._eventReactions={touchmove:{action:this.touchmove,defaultTargetState:"IN_PROGRESS"},touchend:{action:this.touchend,defaultTargetState:"IDLE"},touchstart:{action:()=>"IDLE",defaultTargetState:"IDLE"}}}get eventReactions(){return this._eventReactions}touchmove(context,payload){const idents=payload.points.map((p=>p.ident)),initialPositions=context.getInitialTouchPointsPositions(idents),currentPositions=payload.points,initialStartAndEndDistance=PointCal.distanceBetweenPoints(initialPositions[0],initialPositions[1]),currentStartAndEndDistance=PointCal.distanceBetweenPoints(currentPositions[0],currentPositions[1]),midPoint=PointCal.linearInterpolation(initialPositions[0],initialPositions[1],.5),currentMidPoint=PointCal.linearInterpolation(currentPositions[0],currentPositions[1],.5),midPointDelta=PointCal.subVector(midPoint,currentMidPoint),boundingRect=context.canvas.getBoundingClientRect(),cameraCenterInWindow={x:boundingRect.left+boundingRect.width/2,y:boundingRect.top+boundingRect.height/2},midPointInViewPort=PointCal.subVector(midPoint,cameraCenterInWindow);let panZoom=Math.abs(currentStartAndEndDistance-initialStartAndEndDistance)>PointCal.distanceBetweenPoints(midPoint,currentMidPoint)?"ZOOMING":"PANNING";switch(context.updateTouchPoints(currentPositions),panZoom){case"ZOOMING":context.alignCoordinateSystem||(midPointInViewPort.y=-midPointInViewPort.y),context.notifyOnZoom(.005*-(initialStartAndEndDistance-currentStartAndEndDistance),midPointInViewPort);break;case"PANNING":context.alignCoordinateSystem||(midPointDelta.y=-midPointDelta.y),context.notifyOnPan(midPointDelta);break;default:console.warn("Unknown panZoom state",panZoom)}}touchend(context,payload){context.removeTouchPoints(payload.points.map((p=>p.ident)))}}function createTouchInputStateMachine(context){return new TemplateStateMachine({IDLE:new IdleState,PENDING:new PendingState,IN_PROGRESS:new InProgressState},"IDLE",context)}class ObservableInputTracker{constructor(canvas,inputPublisher){this._alignCoordinateSystem=!0,this._canvas=canvas,this._inputPublisher=inputPublisher,this._initialCursorPosition={x:0,y:0}}get alignCoordinateSystem(){return this._alignCoordinateSystem}get canvas(){return this._canvas}get initialCursorPosition(){return this._initialCursorPosition}set alignCoordinateSystem(value){this._alignCoordinateSystem=value}notifyOnPan(delta){this._inputPublisher.notifyPan(delta)}notifyOnZoom(zoomAmount,anchorPoint){this._inputPublisher.notifyZoom(zoomAmount,anchorPoint)}notifyOnRotate(deltaRotation){this._inputPublisher.notifyRotate(deltaRotation)}setInitialCursorPosition(position){this._initialCursorPosition=position}cleanup(){}setup(){}}class TouchInputTracker{constructor(canvas,inputPublisher){this._touchPointsMap=new Map,this._canvas=canvas,this._inputPublisher=inputPublisher,this._alignCoordinateSystem=!0}addTouchPoints(points){points.forEach((point=>{this._touchPointsMap.set(point.ident,Object.assign({},point))}))}removeTouchPoints(identifiers){identifiers.forEach((ident=>{this._touchPointsMap.has(ident)&&this._touchPointsMap.delete(ident)}))}getCurrentTouchPointsCount(){return this._touchPointsMap.size}getInitialTouchPointsPositions(idents){const res=[];return idents.forEach((ident=>{if(this._touchPointsMap.has(ident)){const point=this._touchPointsMap.get(ident);point&&res.push(point)}})),res}updateTouchPoints(pointsMoved){pointsMoved.forEach((point=>{this._touchPointsMap.has(point.ident)&&this._touchPointsMap.set(point.ident,Object.assign({},point))}))}notifyOnPan(delta){this._inputPublisher.notifyPan(delta)}notifyOnZoom(zoomAmount,anchorPoint){this._inputPublisher.notifyZoom(zoomAmount,anchorPoint)}get canvas(){return this._canvas}get alignCoordinateSystem(){return this._alignCoordinateSystem}set alignCoordinateSystem(value){this._alignCoordinateSystem=value}cleanup(){}setup(){}}export{IdleState,InProgressState,InitialPanState,KmtIdleState,ObservableInputTracker,PanState,PanViaScrollWheelState,PendingState,ReadyToPanViaScrollWheelState,ReadyToPanViaSpaceBarState,ReadyToSelectState,TouchInputTracker,convertFromWindow2ViewPort,createKmtInputStateMachine,createTouchInputStateMachine}; //# sourceMappingURL=index.js.map