|  | @@ -1,11 +1,18 @@
 | 
											
												
													
														|  |  import { AbstractDisplayInteractionManager } from "./AbstractDisplayInteractionManager";
 |  |  import { AbstractDisplayInteractionManager } from "./AbstractDisplayInteractionManager";
 | 
											
												
													
														|  |  import { PointF2D } from "../Common/DataObjects/PointF2D";
 |  |  import { PointF2D } from "../Common/DataObjects/PointF2D";
 | 
											
												
													
														|  | 
 |  | +import { Dictionary } from "typescript-collections";
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |  export class WebDisplayInteractionManager extends AbstractDisplayInteractionManager {
 |  |  export class WebDisplayInteractionManager extends AbstractDisplayInteractionManager {
 | 
											
												
													
														|  |      protected isTouchDevice: boolean = false;
 |  |      protected isTouchDevice: boolean = false;
 | 
											
												
													
														|  |      protected osmdSheetMusicContainer: HTMLElement;
 |  |      protected osmdSheetMusicContainer: HTMLElement;
 | 
											
												
													
														|  |      protected fullOffsetLeft: number = 0;
 |  |      protected fullOffsetLeft: number = 0;
 | 
											
												
													
														|  |      protected fullOffsetTop: number = 0;
 |  |      protected fullOffsetTop: number = 0;
 | 
											
												
													
														|  | 
 |  | +    protected fullScrollTop: number = 0;
 | 
											
												
													
														|  | 
 |  | +    protected fullScrollLeft: number = 0;
 | 
											
												
													
														|  | 
 |  | +    //Using map instead of collections dictionary because map supports using objects as keys properly
 | 
											
												
													
														|  | 
 |  | +    protected parentScrollMap: Map<HTMLElement, number[]> = new Map<HTMLElement, number[]>();
 | 
											
												
													
														|  | 
 |  | +    protected scrollCallbackMap: Map<HTMLElement, (this: HTMLElement, ev: Event) => any> =
 | 
											
												
													
														|  | 
 |  | +                                 new Map<HTMLElement, (this: HTMLElement, ev: Event) => any>();
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |      constructor(osmdContainer: HTMLElement) {
 |  |      constructor(osmdContainer: HTMLElement) {
 | 
											
												
													
														|  |          super();
 |  |          super();
 | 
											
										
											
												
													
														|  | @@ -14,22 +21,136 @@ export class WebDisplayInteractionManager extends AbstractDisplayInteractionMana
 | 
											
												
													
														|  |          this.listenForInteractions();
 |  |          this.listenForInteractions();
 | 
											
												
													
														|  |      }
 |  |      }
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | 
 |  | +    public get FullOffsetTop(): number {
 | 
											
												
													
														|  | 
 |  | +        return this.fullOffsetTop;
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    public get FullScrollTop(): number {
 | 
											
												
													
														|  | 
 |  | +        return this.fullScrollTop;
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    public get FullOffsetLeft(): number {
 | 
											
												
													
														|  | 
 |  | +        return this.fullOffsetLeft;
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    public get FullScrollLeft(): number {
 | 
											
												
													
														|  | 
 |  | +        return this.fullScrollLeft;
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    protected timeout: NodeJS.Timeout = undefined;
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    protected static resizeCallback(entries: ResizeObserverEntry[]|HTMLElement[], self: WebDisplayInteractionManager): void {
 | 
											
												
													
														|  | 
 |  | +        //debounce resize callback
 | 
											
												
													
														|  | 
 |  | +        clearTimeout(self.timeout);
 | 
											
												
													
														|  | 
 |  | +        self.timeout = setTimeout(()=> {
 | 
											
												
													
														|  | 
 |  | +            self.fullOffsetLeft = 0;
 | 
											
												
													
														|  | 
 |  | +            self.fullOffsetTop = 0;
 | 
											
												
													
														|  | 
 |  | +            let nextOffsetParent: HTMLElement = self.osmdSheetMusicContainer;
 | 
											
												
													
														|  | 
 |  | +            while (nextOffsetParent) {
 | 
											
												
													
														|  | 
 |  | +                self.fullOffsetLeft += nextOffsetParent.offsetLeft;
 | 
											
												
													
														|  | 
 |  | +                self.fullOffsetTop += nextOffsetParent.offsetTop;
 | 
											
												
													
														|  | 
 |  | +                nextOffsetParent = nextOffsetParent.offsetParent as HTMLElement;
 | 
											
												
													
														|  | 
 |  | +            }
 | 
											
												
													
														|  | 
 |  | +            self.resizeEventListener();
 | 
											
												
													
														|  | 
 |  | +            self.deregisterScrollOffsets();
 | 
											
												
													
														|  | 
 |  | +            self.registerScrollOffsets();
 | 
											
												
													
														|  | 
 |  | +        }, 500);
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    protected registerScrollOffsets(): void {
 | 
											
												
													
														|  | 
 |  | +        let nextScrollParent: HTMLElement = this.osmdSheetMusicContainer;
 | 
											
												
													
														|  | 
 |  | +        this.fullScrollTop = 0;
 | 
											
												
													
														|  | 
 |  | +        this.fullScrollLeft = 0;
 | 
											
												
													
														|  | 
 |  | +        const self: WebDisplayInteractionManager = this;
 | 
											
												
													
														|  | 
 |  | +        while(nextScrollParent && nextScrollParent !== document.documentElement){
 | 
											
												
													
														|  | 
 |  | +            this.parentScrollMap.set(nextScrollParent, [nextScrollParent.scrollTop, nextScrollParent.scrollLeft]);
 | 
											
												
													
														|  | 
 |  | +            this.fullScrollLeft += nextScrollParent.scrollLeft;
 | 
											
												
													
														|  | 
 |  | +            this.fullScrollTop += nextScrollParent.scrollTop;
 | 
											
												
													
														|  | 
 |  | +            if(nextScrollParent.scrollHeight > nextScrollParent.clientHeight){
 | 
											
												
													
														|  | 
 |  | +                const nextScrollCallback: (this: HTMLElement, ev: Event) => any = function(scrollEvent: Event): void{
 | 
											
												
													
														|  | 
 |  | +                    //@ts-ignore
 | 
											
												
													
														|  | 
 |  | +                    const currentScroll: number[] = self.parentScrollMap.get(this);
 | 
											
												
													
														|  | 
 |  | +                    const currentScrollTop: number = currentScroll[0];
 | 
											
												
													
														|  | 
 |  | +                    const currentScrollLeft: number = currentScroll[1];
 | 
											
												
													
														|  | 
 |  | +                    //@ts-ignore
 | 
											
												
													
														|  | 
 |  | +                    self.fullScrollTop = self.fullScrollTop - currentScrollTop + this.scrollTop;
 | 
											
												
													
														|  | 
 |  | +                    //@ts-ignore
 | 
											
												
													
														|  | 
 |  | +                    self.fullScrollLeft = self.fullScrollLeft - currentScrollLeft + this.scrollLeft;
 | 
											
												
													
														|  | 
 |  | +                    //@ts-ignore
 | 
											
												
													
														|  | 
 |  | +                    self.parentScrollMap.set(this, [this.scrollTop, this.scrollLeft]);
 | 
											
												
													
														|  | 
 |  | +                };
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +                this.scrollCallbackMap.set(nextScrollParent, nextScrollCallback);
 | 
											
												
													
														|  | 
 |  | +                nextScrollParent.addEventListener("scroll", nextScrollCallback);
 | 
											
												
													
														|  | 
 |  | +            }
 | 
											
												
													
														|  | 
 |  | +            nextScrollParent = nextScrollParent.parentElement;
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    protected deregisterScrollOffsets(): void {
 | 
											
												
													
														|  | 
 |  | +        for(const key of this.scrollCallbackMap.keys()){
 | 
											
												
													
														|  | 
 |  | +            key.removeEventListener("scroll", this.scrollCallbackMap.get(key));
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +        this.scrollCallbackMap.clear();
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    protected disposeResizeListener: Function;
 | 
											
												
													
														|  | 
 |  | +    protected resizeObserver: ResizeObserver = undefined;
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  |      protected initialize(): void {
 |  |      protected initialize(): void {
 | 
											
												
													
														|  |          this.fullOffsetLeft = 0;
 |  |          this.fullOffsetLeft = 0;
 | 
											
												
													
														|  |          this.fullOffsetTop = 0;
 |  |          this.fullOffsetTop = 0;
 | 
											
												
													
														|  | -        let nextParent: HTMLElement = this.osmdSheetMusicContainer;
 |  | 
 | 
											
												
													
														|  | -        while (nextParent) {
 |  | 
 | 
											
												
													
														|  | -            this.fullOffsetLeft += nextParent.offsetLeft;
 |  | 
 | 
											
												
													
														|  | -            this.fullOffsetTop += nextParent.offsetTop;
 |  | 
 | 
											
												
													
														|  | -            nextParent = nextParent.offsetParent as HTMLElement;
 |  | 
 | 
											
												
													
														|  | 
 |  | +        let nextOffsetParent: HTMLElement = this.osmdSheetMusicContainer;
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        const entries: HTMLElement[] = [];
 | 
											
												
													
														|  | 
 |  | +        const self: WebDisplayInteractionManager = this;
 | 
											
												
													
														|  | 
 |  | +        if(ResizeObserver){
 | 
											
												
													
														|  | 
 |  | +            this.resizeObserver = new ResizeObserver((observedElements: ResizeObserverEntry[]) => {
 | 
											
												
													
														|  | 
 |  | +                WebDisplayInteractionManager.resizeCallback(observedElements, self);
 | 
											
												
													
														|  | 
 |  | +            });
 | 
											
												
													
														|  |          }
 |  |          }
 | 
											
												
													
														|  | 
 |  | +        while (nextOffsetParent) {
 | 
											
												
													
														|  | 
 |  | +            this.fullOffsetLeft += nextOffsetParent.offsetLeft;
 | 
											
												
													
														|  | 
 |  | +            this.fullOffsetTop += nextOffsetParent.offsetTop;
 | 
											
												
													
														|  | 
 |  | +            if(!ResizeObserver){
 | 
											
												
													
														|  | 
 |  | +                entries.push(nextOffsetParent);
 | 
											
												
													
														|  | 
 |  | +            } else {
 | 
											
												
													
														|  | 
 |  | +                this.resizeObserver.observe(nextOffsetParent);
 | 
											
												
													
														|  | 
 |  | +            }
 | 
											
												
													
														|  | 
 |  | +            nextOffsetParent = nextOffsetParent.offsetParent as HTMLElement;
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        if(!ResizeObserver){
 | 
											
												
													
														|  | 
 |  | +            let resizeListener: (this: Window, ev: UIEvent) => any = (): void => {
 | 
											
												
													
														|  | 
 |  | +                WebDisplayInteractionManager.resizeCallback(entries, self);
 | 
											
												
													
														|  | 
 |  | +            };
 | 
											
												
													
														|  | 
 |  | +            //Resize observer not avail. on this browser, default to window event
 | 
											
												
													
														|  | 
 |  | +            window.addEventListener("resize", resizeListener);
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +            this.disposeResizeListener = (): void => {
 | 
											
												
													
														|  | 
 |  | +                window.removeEventListener("resize", resizeListener);
 | 
											
												
													
														|  | 
 |  | +                resizeListener = undefined;
 | 
											
												
													
														|  | 
 |  | +            };
 | 
											
												
													
														|  | 
 |  | +        } else {
 | 
											
												
													
														|  | 
 |  | +            this.disposeResizeListener = (): void => {
 | 
											
												
													
														|  | 
 |  | +                self.resizeObserver.disconnect();
 | 
											
												
													
														|  | 
 |  | +                self.resizeObserver = undefined;
 | 
											
												
													
														|  | 
 |  | +            };
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +        self.registerScrollOffsets();
 | 
											
												
													
														|  |      }
 |  |      }
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |      protected dispose(): void {
 |  |      protected dispose(): void {
 | 
											
												
													
														|  | -        this.osmdSheetMusicContainer.removeEventListener(this.downEventName, this.downEventListener);
 |  | 
 | 
											
												
													
														|  | -        this.osmdSheetMusicContainer.removeEventListener("touchend", this.touchEndEventListener.bind(this));
 |  | 
 | 
											
												
													
														|  | -        this.osmdSheetMusicContainer.removeEventListener(this.moveEventName, this.moveEventListener);
 |  | 
 | 
											
												
													
														|  | -        window.removeEventListener("resize", this.resizeEventListener.bind(this));
 |  | 
 | 
											
												
													
														|  | 
 |  | +        this.disposeResizeListener();
 | 
											
												
													
														|  | 
 |  | +        for(const eventName of this.EventCallbackMap.keys()){
 | 
											
												
													
														|  | 
 |  | +            const result: [HTMLElement|Document, EventListener] = this.EventCallbackMap.getValue(eventName);
 | 
											
												
													
														|  | 
 |  | +            result[0].removeEventListener(eventName, result[1]);
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +        this.EventCallbackMap.clear();
 | 
											
												
													
														|  | 
 |  | +        this.deregisterScrollOffsets();
 | 
											
												
													
														|  | 
 |  | +        this.scrollCallbackMap.clear();
 | 
											
												
													
														|  | 
 |  | +        this.parentScrollMap.clear();
 | 
											
												
													
														|  |      }
 |  |      }
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |      //TODO: Much of this pulled from annotations code. Once we get the two branches together, combine common code
 |  |      //TODO: Much of this pulled from annotations code. Once we get the two branches together, combine common code
 | 
											
										
											
												
													
														|  | @@ -51,17 +172,19 @@ export class WebDisplayInteractionManager extends AbstractDisplayInteractionMana
 | 
											
												
													
														|  |      protected get moveEventName(): string {
 |  |      protected get moveEventName(): string {
 | 
											
												
													
														|  |          return this.isTouchDevice ? "touchmove" : "mousemove";
 |  |          return this.isTouchDevice ? "touchmove" : "mousemove";
 | 
											
												
													
														|  |      }
 |  |      }
 | 
											
												
													
														|  | 
 |  | +    protected EventCallbackMap: Dictionary<string, [HTMLElement|Document, EventListener]> =
 | 
											
												
													
														|  | 
 |  | +                new Dictionary<string, [HTMLElement|Document, EventListener]>();
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |      private listenForInteractions(): void {
 |  |      private listenForInteractions(): void {
 | 
											
												
													
														|  | -        const self: WebDisplayInteractionManager = this;
 |  | 
 | 
											
												
													
														|  | -        //this.osmdSheetMusicContainer[this.downEventName] = this.downEventListener.bind(this);
 |  | 
 | 
											
												
													
														|  | -        this.osmdSheetMusicContainer.addEventListener(this.downEventName, this.downEventListener.bind(this));
 |  | 
 | 
											
												
													
														|  | -
 |  | 
 | 
											
												
													
														|  | -        this.osmdSheetMusicContainer.addEventListener("touchend", this.touchEndEventListener.bind(this));
 |  | 
 | 
											
												
													
														|  | -
 |  | 
 | 
											
												
													
														|  | -        document.addEventListener(self.moveEventName, this.moveEventListener.bind(this));
 |  | 
 | 
											
												
													
														|  | -
 |  | 
 | 
											
												
													
														|  | -        window.addEventListener("resize", this.resizeEventListener.bind(this));
 |  | 
 | 
											
												
													
														|  | 
 |  | +        const downEvent: (clickEvent: MouseEvent | TouchEvent) => void = this.downEventListener.bind(this);
 | 
											
												
													
														|  | 
 |  | +        const endTouchEvent: (clickEvent: TouchEvent) => void = this.touchEndEventListener.bind(this);
 | 
											
												
													
														|  | 
 |  | +        const moveEvent: (clickEvent: MouseEvent | TouchEvent) => void = this.moveEventListener.bind(this);
 | 
											
												
													
														|  | 
 |  | +        this.osmdSheetMusicContainer.addEventListener(this.downEventName, downEvent);
 | 
											
												
													
														|  | 
 |  | +        this.osmdSheetMusicContainer.addEventListener("touchend", endTouchEvent);
 | 
											
												
													
														|  | 
 |  | +        document.addEventListener(this.moveEventName, moveEvent);
 | 
											
												
													
														|  | 
 |  | +        this.EventCallbackMap.setValue(this.downEventName, [this.osmdSheetMusicContainer, downEvent]);
 | 
											
												
													
														|  | 
 |  | +        this.EventCallbackMap.setValue("touchend", [this.osmdSheetMusicContainer, endTouchEvent]);
 | 
											
												
													
														|  | 
 |  | +        this.EventCallbackMap.setValue(this.moveEventName, [document, moveEvent]);
 | 
											
												
													
														|  |      }
 |  |      }
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |      //Millis of how long is valid for the next click of a double click
 |  |      //Millis of how long is valid for the next click of a double click
 | 
											
										
											
												
													
														|  | @@ -102,12 +225,18 @@ export class WebDisplayInteractionManager extends AbstractDisplayInteractionMana
 | 
											
												
													
														|  |      }
 |  |      }
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |      private moveEventListener(mouseMoveEvent: MouseEvent | TouchEvent): void {
 |  |      private moveEventListener(mouseMoveEvent: MouseEvent | TouchEvent): void {
 | 
											
												
													
														|  | -        //mouseMoveEvent.preventDefault();
 |  | 
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  |          let x: number = 0;
 |  |          let x: number = 0;
 | 
											
												
													
														|  |          let y: number = 0;
 |  |          let y: number = 0;
 | 
											
												
													
														|  |          if (this.isTouchDevice && mouseMoveEvent instanceof TouchEvent) {
 |  |          if (this.isTouchDevice && mouseMoveEvent instanceof TouchEvent) {
 | 
											
												
													
														|  | -            x = mouseMoveEvent.touches[0].clientX;
 |  | 
 | 
											
												
													
														|  | -            y = mouseMoveEvent.touches[0].clientY;
 |  | 
 | 
											
												
													
														|  | 
 |  | +            let touch: Touch = undefined;
 | 
											
												
													
														|  | 
 |  | +            if(mouseMoveEvent.touches && mouseMoveEvent.touches.length > 0){
 | 
											
												
													
														|  | 
 |  | +                touch = mouseMoveEvent.touches[0];
 | 
											
												
													
														|  | 
 |  | +            } else if(mouseMoveEvent.changedTouches && mouseMoveEvent.changedTouches.length > 0){
 | 
											
												
													
														|  | 
 |  | +                touch = mouseMoveEvent.changedTouches[0];
 | 
											
												
													
														|  | 
 |  | +            }
 | 
											
												
													
														|  | 
 |  | +            x = touch?.clientX;
 | 
											
												
													
														|  | 
 |  | +            y = touch?.clientY;
 | 
											
												
													
														|  |          } else if (mouseMoveEvent instanceof MouseEvent) {
 |  |          } else if (mouseMoveEvent instanceof MouseEvent) {
 | 
											
												
													
														|  |              x = mouseMoveEvent.clientX;
 |  |              x = mouseMoveEvent.clientX;
 | 
											
												
													
														|  |              y = mouseMoveEvent.clientY;
 |  |              y = mouseMoveEvent.clientY;
 | 
											
										
											
												
													
														|  | @@ -117,7 +246,13 @@ export class WebDisplayInteractionManager extends AbstractDisplayInteractionMana
 | 
											
												
													
														|  |      }
 |  |      }
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |      private touchEndEventListener(clickEvent: TouchEvent): void {
 |  |      private touchEndEventListener(clickEvent: TouchEvent): void {
 | 
											
												
													
														|  | -        const touchMinusOffset: PointF2D = this.getOffsetCoordinates(clickEvent.touches[0].pageX, clickEvent.touches[0].pageY);
 |  | 
 | 
											
												
													
														|  | 
 |  | +        let touch: Touch = undefined;
 | 
											
												
													
														|  | 
 |  | +        if(clickEvent.touches && clickEvent.touches.length > 0){
 | 
											
												
													
														|  | 
 |  | +            touch = clickEvent.touches[0];
 | 
											
												
													
														|  | 
 |  | +        } else if(clickEvent.changedTouches && clickEvent.changedTouches.length > 0){
 | 
											
												
													
														|  | 
 |  | +            touch = clickEvent.changedTouches[0];
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +        const touchMinusOffset: PointF2D = this.getOffsetCoordinates(touch?.pageX, touch?.pageY);
 | 
											
												
													
														|  |          this.touchUp(touchMinusOffset.x, touchMinusOffset.y);
 |  |          this.touchUp(touchMinusOffset.x, touchMinusOffset.y);
 | 
											
												
													
														|  |      }
 |  |      }
 | 
											
												
													
														|  |  
 |  |  
 | 
											
										
											
												
													
														|  | @@ -127,8 +262,8 @@ export class WebDisplayInteractionManager extends AbstractDisplayInteractionMana
 | 
											
												
													
														|  |      }
 |  |      }
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |      private getOffsetCoordinates(clickX: number, clickY: number): PointF2D {
 |  |      private getOffsetCoordinates(clickX: number, clickY: number): PointF2D {
 | 
											
												
													
														|  | -        const sheetX: number = clickX - this.fullOffsetLeft;
 |  | 
 | 
											
												
													
														|  | -        const sheetY: number = clickY - this.fullOffsetTop;
 |  | 
 | 
											
												
													
														|  | 
 |  | +        const sheetX: number = clickX - this.fullOffsetLeft + this.fullScrollLeft;
 | 
											
												
													
														|  | 
 |  | +        const sheetY: number = clickY - this.fullOffsetTop + this.fullScrollTop;
 | 
											
												
													
														|  |          return new PointF2D(sheetX, sheetY);
 |  |          return new PointF2D(sheetX, sheetY);
 | 
											
												
													
														|  |      }
 |  |      }
 | 
											
												
													
														|  |  }
 |  |  }
 |