<commit_before>import draw2d from "draw2d";
import PanPolicy from "./PanPolicy";
import ZoomPolicy from "./ZoomPolicy"
import KeyboardPolicy from "./KeyboardPolicy";
import ConnectionCreatePolicy from "./ConnectionCreatePolicy"
import Colors from "./Colors";
import { random } from "../../common/utils";
import CanvasSerializer from "./CanvasSerializer";

const randomDist = 30

export default class Canvas extends draw2d.Canvas {
    serializer = null

    diagramId = null
    canvasId = null
    mainNodeId = null

    touchStartTime = 0
    touchEndTime = 0
    previousPinchDiff = -1

    constructor(htmlElementId, onEditMode, width, height) {
        super(htmlElementId, width, height);

        this.serializer = new CanvasSerializer(this)

        this.setScrollArea("#" + htmlElementId)
        this.setDimension(new draw2d.geo.Rectangle(0, 0, width, height))

        // A likely bug in draw2d can be fixed with this hack
        this.regionDragDropConstraint.constRect = new draw2d.geo.Rectangle(0, 0, width, height)

        // Center the canvas
        const area = this.getScrollArea()
        area.scrollLeft(width / 2 - this.getWidth() / 2)
        area.scrollTop(height / 2 - this.getHeight() / 2)

        this.panPolicy = new PanPolicy(onEditMode)
        this.installEditPolicy(this.panPolicy)

        this.zoomPolicy = new ZoomPolicy()
        this.installEditPolicy(this.zoomPolicy);

        this.installEditPolicy(new ConnectionCreatePolicy())
        this.installEditPolicy(new draw2d.policy.canvas.CoronaDecorationPolicy());

        this.setNormalBackground()

        this.installEditPolicy(new draw2d.policy.canvas.SnapToGeometryEditPolicy())
        this.installEditPolicy(new draw2d.policy.canvas.SnapToInBetweenEditPolicy())
        this.installEditPolicy(new draw2d.policy.canvas.SnapToCenterEditPolicy())
        this.installEditPolicy(new KeyboardPolicy())

        this.enableTouchSupport()

        //canvas.installEditPolicy(new draw2d.policy.canvas.SnapToGridEditPolicy(10, false))

    }

    serialize() {
        return this.serializer.serialize()
    }

    deserialize(canvasData) {
        this.serializer.deserialize(canvasData)
    }

    export(resultHandler) {
        const rect = this.getFiguresRect()
        this.serializer.export(rect, resultHandler)
    }

    clearDiagram = () => {
        const canvas = this
        canvas.lines.clone().each((i, e) => canvas.remove(e))
        canvas.figures.clone().each((i, e) => canvas.remove(e))
        canvas.selection.clear()
        canvas.currentDropTarget = null
        canvas.figures = new draw2d.util.ArrayList()
        canvas.lines = new draw2d.util.ArrayList()
        canvas.commonPorts = new draw2d.util.ArrayList()
        canvas.commandStack.markSaveLocation()
        canvas.linesToRepaintAfterDragDrop = new draw2d.util.ArrayList()
        canvas.lineIntersections = new draw2d.util.ArrayList()
        canvas.diagramId = null
        canvas.canvasId = null
    }

    runCmd(command) {
        this.getCommandStack().execute(command);
    }

    setNormalBackground() {
        this.html.find("svg").css({
            'background-color': Colors.canvasDivBackground,
            "background": Colors.canvasDivBackground,
            "background-size": 0
        })
    }

    setGridBackground() {
        // In edit mode, add a grid background.
        const bgColor = Colors.canvasDivBackground
        const gridColor = Colors.canvasGridRgb
        const interval = 10
        const gridStroke = 1

        let background =
            ` linear-gradient(to right,  ${gridColor} ${gridStroke}px, transparent ${gridStroke}px),
              linear-gradient(to bottom, ${gridColor} ${gridStroke}px, ${bgColor}  ${gridStroke}px)`
        let backgroundSize = `${interval}px ${interval}px`

        this.html.find("svg").css({
            "background": background,
            "background-size": backgroundSize
        })
    }

    addAtApproximately(figure, x, y) {
        if (null != this.getFigures().asArray().find(f => f.x === x && f.y === y)) {
            // Figure exists at that place, lets retry with other coordinate
            x = x + random(-randomDist, randomDist)
            y = y + random(-randomDist, randomDist)
            this.addAtApproximately(figure, x, y)
            return
        }

        // No other figure at this place, lets add
        this.add(figure, x, y)
    }

    addAll(figures) {
        //const t = timing()
        for (let i = 0; i < figures.length; i++) {
            let figure = figures[i]
            let x = figures[i].x
            let y = figures[i].y;

            if (figure.getCanvas() === this) {
                return;
            }

            if (figure instanceof draw2d.shape.basic.Line) {
                this.lines.add(figure);
            } else {
                this.figures.add(figure);
                if (typeof y !== "undefined") {
                    figure.setPosition(x, y);
                } else if (typeof x !== "undefined") {
                    figure.setPosition(x);
                }
            }

            figure.setCanvas(this);

            // to avoid drag&drop outside of this canvas
            figure.installEditPolicy(this.regionDragDropConstraint);

            // important initial call
            figure.getShapeElement();

            // fire the figure:add event before the "move" event and after the figure.repaint() call!
            //   - the move event can only be fired if the figure part of the canvas.
            //     and in this case the notification event should be fired to the listener before
            this.fireEvent("figure:add", { figure: figure, canvas: this });

            // fire the event that the figure is part of the canvas
            figure.fireEvent("added", { figure: figure, canvas: this });

            // ...now we can fire the initial move event
            figure.fireEvent("move", { figure: figure, dx: 0, dy: 0 });

            if (figure instanceof draw2d.shape.basic.PolyLine) {
                this.calculateConnectionIntersection();
            }
        }
        //t.log("Added all figures");

        this.figures.each(function (i, fig) {
            fig.repaint();
        });
        //t.log("Repainted figures");

        this.lines.each(function (i, line) {
            line.svgPathString = null;
            line.repaint();

        });
        //t.log();
        return this;
    }

    getFiguresRect() {
        const d = this.getDimension()
        let minX = d.getWidth()
        let minY = d.getHeight()
        let maxX = 0
        let maxY = 0

        this.getFigures().each((i, f) => {
            let fx = f.getAbsoluteX()
            let fy = f.getAbsoluteY()
            let fx2 = fx + f.getWidth()
            let fy2 = fy + f.getHeight()

            if (i === 0) {
                minX = fx
                minY = fy
                maxX = fx2
                maxY = fy2
                return
            }

            if (fx < minX) {
                minX = fx
            }
            if (fy < minY) {
                minY = fy
            }
            if (fx2 > maxX) {
                maxX = fx2
            }
            if (fy2 > maxY) {
                maxY = fy2
            }
        })

        return { x: minX, y: minY, w: maxX - minX, h: maxY - minY, x2: maxX, y2: maxY }
    }

    enableTouchSupport() {
        // Seems that the parent does nog handle touchstart same as a mouse down event
        this.html.bind("touchstart", (event) => this.handleTouchStart(event))

        //Seems that the parent does nog handle click and double-click for touchend
        this.html.bind("touchend", (event) => this.handleTouchEnd(event))

        // Need to replace the parent handler of mouse and touch move to enable multi touch as well
        this.html.unbind("mousemove touchmove")
        this.html.bind("mousemove touchmove", (event) => {
            if (event.type === 'touchmove' && event.touches.length === 2) {
                this.handleMultiTouchMove(event)
                return
            }

            this.handleMouseTouchMove(event)
        })
    }

    // Handle touch start same as if mouse down in parent canvas
    handleTouchStart(event) {
        try {
            let pos = null
            switch (event.which) {
                case 1: //touch pressed
                case 0: //Left mouse button pressed or touch
                    try {
                        event.preventDefault()
                        event = this._getEvent(event)
                        this.mouseDownX = event.clientX
                        this.mouseDownY = event.clientY
                        this.mouseDragDiffX = 0
                        this.mouseDragDiffY = 0
                        this.previousPinchDiff = -1
                        this.touchStartTime = performance.now()
                        pos = this.fromDocumentToCanvasCoordinate(event.clientX, event.clientY)
                        this.mouseDown = true
                        this.editPolicy.each((i, policy) => {
                            policy.onMouseDown(this, pos.x, pos.y, event.shiftKey, event.ctrlKey)
                        })
                    } catch (exc) {
                        console.log(exc)
                    }
                    break
                case 3: //Right mouse button pressed
                    event.preventDefault()
                    if (typeof event.stopPropagation !== "undefined")
                        event.stopPropagation()
                    event = this._getEvent(event)
                    pos = this.fromDocumentToCanvasCoordinate(event.clientX, event.clientY)
                    this.onRightMouseDown(pos.x, pos.y, event.shiftKey, event.ctrlKey)
                    return false
                case 2:
                    //Middle mouse button pressed
                    break
                default:
                //You have a strange mouse
            }
        } catch (exc) {
            console.log(exc)
        }
    }

    // Handle touch end to support touch click, double-click and long click to simulate right click
    // for context menu
    handleTouchEnd(event) {
        const longClickTimeout = 500
        const maxDist = 10

        // Calculate click length and double click interval
        const clickTime = performance.now() - this.touchStartTime
        const clickInterval = performance.now() - this.touchEndTime
        this.touchEndTime = performance.now()

        if (event.touches?.length > 0) {
            // Multi touch ends for one touch, skip this event since neither click nor double click
            this.touchEndTime = 0
            this.pinchDiff = -1
            return
        }

        event = this._getEvent(event)

        if (this.mouseDownX === event.clientX || this.mouseDownY === event.clientY) {
            // Handle click for touch events
            let pos = this.fromDocumentToCanvasCoordinate(event.clientX, event.clientY)
            this.onClick(pos.x, pos.y, event.shiftKey, event.ctrlKey)
            console.log('click')
        }

        if (clickTime > longClickTimeout &&
            (Math.abs(this.mouseDownX - event.clientX) < maxDist &&
                Math.abs(this.mouseDownY - event.clientY) < maxDist)) {
            // long click to simulate context menu
            document.dispatchEvent(new CustomEvent('longclick', { detail: event }));
        }

        if (clickInterval < 500) {
            // Handle double-click event for touch
            this.touchDownX = event.clientX
            this.touchDownY = event.clientY
            let pos = this.fromDocumentToCanvasCoordinate(event.clientX, event.clientY)
            this.onDoubleClick(pos.x, pos.y, event.shiftKey, event.ctrlKey)
        }
    }

    // Handle touch move when two touch (pinch zoom)
    handleMultiTouchMove(event) {
        let pinchDelta = 200 * this.zoomFactor
        const t1 = event.touches[0]
        const t2 = event.touches[1]
        const currentDiff = this.distance(t1.clientX, t1.clientY, t2.clientX, t2.clientY)

        if (this.previousPinchDiff === -1) {
            // First event for pinch (lets just save diff and to be used in next event)
            this.previousPinchDiff = currentDiff
            return
        }

        if (currentDiff > this.previousPinchDiff) {
            // The distance between the two pointers has increased (zoom in), reverse pinch delta
            pinchDelta = -pinchDelta
        }

        // Store diff to be used in next event
        this.previousPinchDiff = currentDiff

        // Calculate the zoom center (middle of the two touch points)
        const x = (t2.clientX - t1.clientX) / 2 + t1.clientX
        const y = (t2.clientY - t1.clientY) / 2 + t1.clientY
        const center = this.fromDocumentToCanvasCoordinate(x, y)

        // Pinch uses same as mouse wheel to zoom
        this.onMouseWheel(pinchDelta, center.x, center.y, false, false)
        return
    }

    // The parent handler of muse move and touch move did not handle multi touch move (pinch)
    handleMouseTouchMove(event) {
        event = this._getEvent(event)
        //console.log('event', event)
        let pos = this.fromDocumentToCanvasCoordinate(event.clientX, event.clientY)
        if (this.mouseDown === false) {
            // mouseEnter/mouseLeave events for Figures. Don't use the Raphael or DOM native functions.
            // Raphael didn't work for Rectangle with transparent fill (events only fired for the border line)
            // DOM didn't work well for lines. No eclipse area - you must hit the line exact to retrieve the event.
            // In this case I implement my own stuff...again and again.
            //
            // don't break the main event loop if one element fires an error during enter/leave event.
            try {
                let hover = this.getBestFigure(pos.x, pos.y)
                if (hover !== this.currentHoverFigure && this.currentHoverFigure !== null) {
                    this.currentHoverFigure.onMouseLeave() // deprecated
                    this.currentHoverFigure.fireEvent("mouseleave")
                    this.fireEvent("mouseleave", { figure: this.currentHoverFigure })
                }
                if (hover !== this.currentHoverFigure && hover !== null) {
                    hover.onMouseEnter()
                    hover.fireEvent("mouseenter")
                    this.fireEvent("mouseenter", { figure: hover })
                }
                this.currentHoverFigure = hover
            } catch (exc) {
                // just write it to the console
                console.log(exc)
            }

            this.editPolicy.each((i, policy) => {
                policy.onMouseMove(this, pos.x, pos.y, event.shiftKey, event.ctrlKey)
            })
            this.fireEvent("mousemove", {
                x: pos.x,
                y: pos.y,
                shiftKey: event.shiftKey,
                ctrlKey: event.ctrlKey,
                hoverFigure: this.currentHoverFigure
            })
        } else {
            let diffXAbs = (event.clientX - this.mouseDownX) * this.zoomFactor
            let diffYAbs = (event.clientY - this.mouseDownY) * this.zoomFactor
            this.editPolicy.each((i, policy) => {
                policy.onMouseDrag(this, diffXAbs, diffYAbs, diffXAbs - this.mouseDragDiffX, diffYAbs - this.mouseDragDiffY, event.shiftKey, event.ctrlKey)
            })
            this.mouseDragDiffX = diffXAbs
            this.mouseDragDiffY = diffYAbs
            this.fireEvent("mousemove", {
                x: pos.x,
                y: pos.y,
                shiftKey: event.shiftKey,
                ctrlKey: event.ctrlKey,
                hoverFigure: this.currentHoverFigure
            })
        }
    }

    distance = (x1, y1, x2, y2) => {
        return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2))
    }
}
<commit_msg>Change long touch to context menu handling<commit_after>import draw2d from "draw2d";
import PanPolicy from "./PanPolicy";
import ZoomPolicy from "./ZoomPolicy"
import KeyboardPolicy from "./KeyboardPolicy";
import ConnectionCreatePolicy from "./ConnectionCreatePolicy"
import Colors from "./Colors";
import { random } from "../../common/utils";
import CanvasSerializer from "./CanvasSerializer";

const randomDist = 30

export default class Canvas extends draw2d.Canvas {
    serializer = null

    diagramId = null
    canvasId = null
    mainNodeId = null

    touchStartTime = 0
    touchEndTime = 0
    previousPinchDiff = -1

    constructor(htmlElementId, onEditMode, width, height) {
        super(htmlElementId, width, height);

        this.serializer = new CanvasSerializer(this)

        this.setScrollArea("#" + htmlElementId)
        this.setDimension(new draw2d.geo.Rectangle(0, 0, width, height))

        // A likely bug in draw2d can be fixed with this hack
        this.regionDragDropConstraint.constRect = new draw2d.geo.Rectangle(0, 0, width, height)

        // Center the canvas
        const area = this.getScrollArea()
        area.scrollLeft(width / 2 - this.getWidth() / 2)
        area.scrollTop(height / 2 - this.getHeight() / 2)

        this.panPolicy = new PanPolicy(onEditMode)
        this.installEditPolicy(this.panPolicy)

        this.zoomPolicy = new ZoomPolicy()
        this.installEditPolicy(this.zoomPolicy);

        this.installEditPolicy(new ConnectionCreatePolicy())
        this.installEditPolicy(new draw2d.policy.canvas.CoronaDecorationPolicy());

        this.setNormalBackground()

        this.installEditPolicy(new draw2d.policy.canvas.SnapToGeometryEditPolicy())
        this.installEditPolicy(new draw2d.policy.canvas.SnapToInBetweenEditPolicy())
        this.installEditPolicy(new draw2d.policy.canvas.SnapToCenterEditPolicy())
        this.installEditPolicy(new KeyboardPolicy())

        this.enableTouchSupport()

        //canvas.installEditPolicy(new draw2d.policy.canvas.SnapToGridEditPolicy(10, false))

    }

    serialize() {
        return this.serializer.serialize()
    }

    deserialize(canvasData) {
        this.serializer.deserialize(canvasData)
    }

    export(resultHandler) {
        const rect = this.getFiguresRect()
        this.serializer.export(rect, resultHandler)
    }

    clearDiagram = () => {
        const canvas = this
        canvas.lines.clone().each((i, e) => canvas.remove(e))
        canvas.figures.clone().each((i, e) => canvas.remove(e))
        canvas.selection.clear()
        canvas.currentDropTarget = null
        canvas.figures = new draw2d.util.ArrayList()
        canvas.lines = new draw2d.util.ArrayList()
        canvas.commonPorts = new draw2d.util.ArrayList()
        canvas.commandStack.markSaveLocation()
        canvas.linesToRepaintAfterDragDrop = new draw2d.util.ArrayList()
        canvas.lineIntersections = new draw2d.util.ArrayList()
        canvas.diagramId = null
        canvas.canvasId = null
    }

    runCmd(command) {
        this.getCommandStack().execute(command);
    }

    setNormalBackground() {
        this.html.find("svg").css({
            'background-color': Colors.canvasDivBackground,
            "background": Colors.canvasDivBackground,
            "background-size": 0
        })
    }

    setGridBackground() {
        // In edit mode, add a grid background.
        const bgColor = Colors.canvasDivBackground
        const gridColor = Colors.canvasGridRgb
        const interval = 10
        const gridStroke = 1

        let background =
            ` linear-gradient(to right,  ${gridColor} ${gridStroke}px, transparent ${gridStroke}px),
              linear-gradient(to bottom, ${gridColor} ${gridStroke}px, ${bgColor}  ${gridStroke}px)`
        let backgroundSize = `${interval}px ${interval}px`

        this.html.find("svg").css({
            "background": background,
            "background-size": backgroundSize
        })
    }

    addAtApproximately(figure, x, y) {
        if (null != this.getFigures().asArray().find(f => f.x === x && f.y === y)) {
            // Figure exists at that place, lets retry with other coordinate
            x = x + random(-randomDist, randomDist)
            y = y + random(-randomDist, randomDist)
            this.addAtApproximately(figure, x, y)
            return
        }

        // No other figure at this place, lets add
        this.add(figure, x, y)
    }

    addAll(figures) {
        //const t = timing()
        for (let i = 0; i < figures.length; i++) {
            let figure = figures[i]
            let x = figures[i].x
            let y = figures[i].y;

            if (figure.getCanvas() === this) {
                return;
            }

            if (figure instanceof draw2d.shape.basic.Line) {
                this.lines.add(figure);
            } else {
                this.figures.add(figure);
                if (typeof y !== "undefined") {
                    figure.setPosition(x, y);
                } else if (typeof x !== "undefined") {
                    figure.setPosition(x);
                }
            }

            figure.setCanvas(this);

            // to avoid drag&drop outside of this canvas
            figure.installEditPolicy(this.regionDragDropConstraint);

            // important initial call
            figure.getShapeElement();

            // fire the figure:add event before the "move" event and after the figure.repaint() call!
            //   - the move event can only be fired if the figure part of the canvas.
            //     and in this case the notification event should be fired to the listener before
            this.fireEvent("figure:add", { figure: figure, canvas: this });

            // fire the event that the figure is part of the canvas
            figure.fireEvent("added", { figure: figure, canvas: this });

            // ...now we can fire the initial move event
            figure.fireEvent("move", { figure: figure, dx: 0, dy: 0 });

            if (figure instanceof draw2d.shape.basic.PolyLine) {
                this.calculateConnectionIntersection();
            }
        }
        //t.log("Added all figures");

        this.figures.each(function (i, fig) {
            fig.repaint();
        });
        //t.log("Repainted figures");

        this.lines.each(function (i, line) {
            line.svgPathString = null;
            line.repaint();

        });
        //t.log();
        return this;
    }

    getFiguresRect() {
        const d = this.getDimension()
        let minX = d.getWidth()
        let minY = d.getHeight()
        let maxX = 0
        let maxY = 0

        this.getFigures().each((i, f) => {
            let fx = f.getAbsoluteX()
            let fy = f.getAbsoluteY()
            let fx2 = fx + f.getWidth()
            let fy2 = fy + f.getHeight()

            if (i === 0) {
                minX = fx
                minY = fy
                maxX = fx2
                maxY = fy2
                return
            }

            if (fx < minX) {
                minX = fx
            }
            if (fy < minY) {
                minY = fy
            }
            if (fx2 > maxX) {
                maxX = fx2
            }
            if (fy2 > maxY) {
                maxY = fy2
            }
        })

        return { x: minX, y: minY, w: maxX - minX, h: maxY - minY, x2: maxX, y2: maxY }
    }

    enableTouchSupport() {
        // Seems that the parent does nog handle touchstart same as a mouse down event
        this.html.bind("touchstart", (event) => this.handleTouchStart(event))

        //Seems that the parent does nog handle click and double-click for touchend
        this.html.bind("touchend", (event) => this.handleTouchEnd(event))

        // Need to replace the parent handler of mouse and touch move to enable multi touch as well
        this.html.unbind("mousemove touchmove")
        this.html.bind("mousemove touchmove", (event) => {
            if (event.type === 'touchmove' && event.touches.length === 2) {
                this.handlePinchTouchMove(event)
                return
            }

            this.handleMouseTouchMove(event)
        })
    }

    // Handle touch start same as if mouse down in parent canvas
    handleTouchStart = (event) => {
        try {
            let pos = null
            switch (event.which) {
                case 1: //touch pressed
                case 0: //Left mouse button pressed or touch
                    try {
                        event.preventDefault()
                        event = this._getEvent(event)
                        this.mouseDownX = event.clientX
                        this.mouseDownY = event.clientY
                        this.mouseDragDiffX = 0
                        this.mouseDragDiffY = 0
                        this.previousPinchDiff = -1
                        this.touchStartTime = performance.now()
                        this.startLongTouchDetection(event)
                        pos = this.fromDocumentToCanvasCoordinate(event.clientX, event.clientY)
                        this.mouseDown = true
                        this.editPolicy.each((i, policy) => {
                            policy.onMouseDown(this, pos.x, pos.y, event.shiftKey, event.ctrlKey)
                        })
                    } catch (exc) {
                        console.log(exc)
                    }
                    break
                case 3: //Right mouse button pressed
                    event.preventDefault()
                    if (typeof event.stopPropagation !== "undefined")
                        event.stopPropagation()
                    event = this._getEvent(event)
                    pos = this.fromDocumentToCanvasCoordinate(event.clientX, event.clientY)
                    this.onRightMouseDown(pos.x, pos.y, event.shiftKey, event.ctrlKey)
                    return false
                case 2:
                    //Middle mouse button pressed
                    break
                default:
                //You have a strange mouse
            }
        } catch (exc) {
            console.log(exc)
        }
    }

    // Handle touch end to support touch click, double-click and long click to simulate right click
    // for context menu
    handleTouchEnd = (event) => {
        this.cancelLongTouch()
        this.pinchDiff = -1

        // Calculate click length and double click interval
        const clickInterval = performance.now() - this.touchEndTime
        this.touchEndTime = performance.now()

        if (this.longTouchHandled) {
            // No click detection when long touch press
            return
        }

        if (event.touches?.length > 0) {
            // Multi touch ends for one touch, skip this event since neither click nor double click
            this.touchEndTime = 0
            return
        }

        event = this._getEvent(event)

        if (this.mouseDownX === event.clientX || this.mouseDownY === event.clientY) {
            // Handle click for touch events
            let pos = this.fromDocumentToCanvasCoordinate(event.clientX, event.clientY)
            this.onClick(pos.x, pos.y, event.shiftKey, event.ctrlKey)
            // console.log('click')
        }

        if (clickInterval < 500) {
            // Handle double-click event for touch
            this.touchDownX = event.clientX
            this.touchDownY = event.clientY
            let pos = this.fromDocumentToCanvasCoordinate(event.clientX, event.clientY)
            this.onDoubleClick(pos.x, pos.y, event.shiftKey, event.ctrlKey)
        }
    }

    // Handle touch move when two touch (pinch zoom)
    handlePinchTouchMove = (event) => {
        this.cancelLongTouch()

        let pinchDelta = 200 * this.zoomFactor
        const t1 = event.touches[0]
        const t2 = event.touches[1]
        const currentDiff = this.distance(t1.clientX, t1.clientY, t2.clientX, t2.clientY)

        if (this.previousPinchDiff === -1) {
            // First event for pinch (lets just save diff and to be used in next event)
            this.previousPinchDiff = currentDiff
            return
        }

        if (currentDiff > this.previousPinchDiff) {
            // The distance between the two pointers has increased (zoom in), reverse pinch delta
            pinchDelta = -pinchDelta
        }

        // Store diff to be used in next event
        this.previousPinchDiff = currentDiff

        // Calculate the zoom center (middle of the two touch points)
        const x = (t2.clientX - t1.clientX) / 2 + t1.clientX
        const y = (t2.clientY - t1.clientY) / 2 + t1.clientY
        const center = this.fromDocumentToCanvasCoordinate(x, y)

        // Pinch uses same as mouse wheel to zoom
        this.onMouseWheel(pinchDelta, center.x, center.y, false, false)
        return
    }

    // The parent handler of muse move and touch move did not handle multi touch move (pinch)
    handleMouseTouchMove = (event) => {
        this.cancelLongTouchIfMove(event)

        event = this._getEvent(event)
        //console.log('event', event)
        let pos = this.fromDocumentToCanvasCoordinate(event.clientX, event.clientY)
        if (this.mouseDown === false) {
            // mouseEnter/mouseLeave events for Figures. Don't use the Raphael or DOM native functions.
            // Raphael didn't work for Rectangle with transparent fill (events only fired for the border line)
            // DOM didn't work well for lines. No eclipse area - you must hit the line exact to retrieve the event.
            // In this case I implement my own stuff...again and again.
            //
            // don't break the main event loop if one element fires an error during enter/leave event.
            try {
                let hover = this.getBestFigure(pos.x, pos.y)
                if (hover !== this.currentHoverFigure && this.currentHoverFigure !== null) {
                    this.currentHoverFigure.onMouseLeave() // deprecated
                    this.currentHoverFigure.fireEvent("mouseleave")
                    this.fireEvent("mouseleave", { figure: this.currentHoverFigure })
                }
                if (hover !== this.currentHoverFigure && hover !== null) {
                    hover.onMouseEnter()
                    hover.fireEvent("mouseenter")
                    this.fireEvent("mouseenter", { figure: hover })
                }
                this.currentHoverFigure = hover
            } catch (exc) {
                // just write it to the console
                console.log(exc)
            }

            this.editPolicy.each((i, policy) => {
                policy.onMouseMove(this, pos.x, pos.y, event.shiftKey, event.ctrlKey)
            })
            this.fireEvent("mousemove", {
                x: pos.x,
                y: pos.y,
                shiftKey: event.shiftKey,
                ctrlKey: event.ctrlKey,
                hoverFigure: this.currentHoverFigure
            })
        } else {
            let diffXAbs = (event.clientX - this.mouseDownX) * this.zoomFactor
            let diffYAbs = (event.clientY - this.mouseDownY) * this.zoomFactor
            this.editPolicy.each((i, policy) => {
                policy.onMouseDrag(this, diffXAbs, diffYAbs, diffXAbs - this.mouseDragDiffX, diffYAbs - this.mouseDragDiffY, event.shiftKey, event.ctrlKey)
            })
            this.mouseDragDiffX = diffXAbs
            this.mouseDragDiffY = diffYAbs
            this.fireEvent("mousemove", {
                x: pos.x,
                y: pos.y,
                shiftKey: event.shiftKey,
                ctrlKey: event.ctrlKey,
                hoverFigure: this.currentHoverFigure
            })
        }
    }

    startLongTouchDetection = (event) => {
        const longPressTimeout = 500
        console.log('Start touch timer')
        this.cancelLongTouch()
        this.longTouchStartEvent = event
        this.longTouchHandled = false
        this.longTouchTimer = setTimeout(() => this.handleLongTouchTimeout(), longPressTimeout);
    }

    handleLongTouchTimeout = () => {
        // console.log('touch timeout')
        this.cancelLongTouch()
        this.longTouchHandled = true
        document.dispatchEvent(new CustomEvent('longclick', { detail: this.longTouchStartEvent }));
    }

    cancelLongTouch = () => {
        clearTimeout(this.longTouchTimer)
    }

    cancelLongTouchIfMove = (event) => {
        if (event.type === 'touchmove' && !this.isClose(event)) {
            this.cancelLongTouch()
        }
    }

    isClose = (event) => {
        const maxDist = 10
        return Math.abs(this.mouseDownX - event.clientX) < maxDist &&
            Math.abs(this.mouseDownY - event.clientY) < maxDist
    }

    distance = (x1, y1, x2, y2) => {
        return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2))
    }
}
<|endoftext|>


<commit_before>import React      from 'react'
import PropTypes  from 'prop-types'
import Radium     from 'radium'

const styles = {
  container: {
    display: 'inline-block'
  },
  main: {
    default: {
      padding: '12px 16px',
      display: 'block',
      fontSize: '14px',
      color: '#3ea327',
      backgroundColor: '#f7f7f7',
      borderRadius: '24px',
      margin: '0 4px',
      lineHeight: '1.2',
      transition: 'background-color 150ms ease-in-out',

      ':hover': {
        textDecoration: 'none',
        backgroundColor: '#eee'
      },
      ':focus': {
        textDecoration: 'none',
        backgroundColor: '#eee',
        outline: 'none'
      }
    },
    active: {
      backgroundColor: '#43B02A',
      color: '#fff',

      ':hover': {
        backgroundColor: '#43B02A'
      },
      ':focus': {
        backgroundColor: '#43B02A',
      }
    }
  }
}

const NavigationPill = props => {
  const { isActive, text } = props

  return (

       props.onClick(e, props)}
        style={[
          styles.main.default,
          isActive && styles.main.active
        ]}
        key={`pill-anchor-${text}`}
      >
        {text}

  )
}

NavigationPill.propTypes = {
  /** determines wether or not active styles are applied */
  isActive: PropTypes.bool,
  /** Callback function called after pill click
   * @param {SyntheticEvent} event The react `SyntheticEvent`
   * @param {props} object All the props passed to the component
  */
  onClick: PropTypes.func,
  /** url used for href property */
  path: PropTypes.string,
  /** text to appear inside pill */
  text: PropTypes.string
}

NavigationPill.defaultProps = {
  isActive: false
}

export default Radium(NavigationPill)
<commit_msg>Move NavigationPill styles to themer.<commit_after>import React      from 'react'
import PropTypes  from 'prop-types'
import Radium     from 'radium'
import colors     from '../../styles/colors'
import themer     from '../../styles/themer'

const styles = {
  container: {
    display: 'inline-block'
  },
  main: {
    default: {
      padding: '12px 16px',
      display: 'block',
      fontSize: '14px',
      color: themer.get('colors', 'primaryForeground'),
      backgroundColor: colors.GRAY_97,
      borderRadius: '24px',
      margin: '0 4px',
      lineHeight: '1.2',
      transition: 'background-color 150ms ease-in-out',

      ':hover': {
        textDecoration: 'none',
        backgroundColor: colors.GRAY_93
      },
      ':focus': {
        textDecoration: 'none',
        backgroundColor: colors.GRAY_93,
        outline: 'none'
      }
    },
    active: {
      backgroundColor: themer.get('colors', 'primaryForeground'),
      color: colors.WHITE,

      ':hover': {
        backgroundColor: themer.get('colors', 'primaryForeground')
      },
      ':focus': {
        backgroundColor: themer.get('colors', 'primaryForeground'),
      }
    }
  }
}

const NavigationPill = props => {
  const { isActive, text } = props

  return (

       props.onClick(e, props)}
        style={[
          styles.main.default,
          isActive && styles.main.active
        ]}
        key={`pill-anchor-${text}`}
      >
        {text}

  )
}

NavigationPill.propTypes = {
  /** determines wether or not active styles are applied */
  isActive: PropTypes.bool,
  /** Callback function called after pill click
   * @param {SyntheticEvent} event The react `SyntheticEvent`
   * @param {props} object All the props passed to the component
  */
  onClick: PropTypes.func,
  /** url used for href property */
  path: PropTypes.string,
  /** text to appear inside pill */
  text: PropTypes.string
}

NavigationPill.defaultProps = {
  isActive: false
}

export default Radium(NavigationPill)
<|endoftext|>


<commit_before>define( [
    'osg/Matrix',
], function ( Matrix ) {

    'use strict';

    var RenderLeaf = function () {

        this._parent = undefined;
        this._geometry = undefined;
        this._depth = 0.0;

        this._projection = undefined;
        this._view = undefined;
        this._modelWorld = undefined;
        this._modelView = undefined;
    };

    RenderLeaf.prototype = {

        reset: function () {
            this._parent = undefined;
            this._geometry = undefined;
            this._depth = 0.0;

            this._projection = undefined;
            this._view = undefined;
            this._modelWorld = undefined;
            this._modelView = undefined;
        },

        init: function ( parent, geom, projection, view, modelView, modelWorld, depth ) {

            this._parent = parent;
            this._geometry = geom;
            this._depth = depth;

            this._projection = projection;
            this._view = view;
            this._modelWorld = modelWorld;
            this._modelView = modelView;

        },

        drawGeometry: ( function () {

            var tempMatrice = Matrix.create();
            var modelViewUniform, viewUniform, modelWorldUniform, projectionUniform, normalUniform, program;

            return function ( state ) {

                var gl = state.getGraphicContext();

                program = state.getLastProgramApplied();

                modelViewUniform = program._uniformsCache[ state.modelViewMatrix.name ];
                modelWorldUniform = program._uniformsCache[ state.modelWorldMatrix.name ];
                viewUniform = program._uniformsCache[ state.viewMatrix.name ];
                projectionUniform = program._uniformsCache[ state.projectionMatrix.name ];
                normalUniform = program._uniformsCache[ state.normalMatrix.name ];

                if ( modelViewUniform !== undefined ) {
                    state.modelViewMatrix.set( this._modelView );
                    state.modelViewMatrix.apply( gl, modelViewUniform );
                }

                if ( modelWorldUniform !== undefined ) {
                    state.modelWorldMatrix.set( this._modelWorld );
                    state.modelWorldMatrix.apply( gl, modelWorldUniform );
                }

                if ( viewUniform !== undefined ) {
                    state.viewMatrix.set( this._view );
                    state.viewMatrix.apply( gl, viewUniform );
                }

                if ( projectionUniform !== undefined ) {
                    state.projectionMatrix.set( this._projection );
                    state.projectionMatrix.apply( gl, projectionUniform );
                }

                if ( normalUniform !== undefined ) {

                    // TODO: optimize the uniform scaling case
                    // where inversion is simpler/faster/shared
                    // but profile before
                    Matrix.copy( this._modelView, tempMatrice );
                    var normal = tempMatrice;
                    normal[ 12 ] = 0.0;
                    normal[ 13 ] = 0.0;
                    normal[ 14 ] = 0.0;

                    Matrix.inverse( normal, normal );
                    Matrix.transpose( normal, normal );
                    state.normalMatrix.set( normal );
                    state.normalMatrix.apply( gl, normalUniform );
                }

                this._geometry.drawImplementation( state );

            };
        } )(),

        render: function ( state, previousLeaf ) {

            var prevRenderGraph;
            var prevRenderGraphParent;
            var rg;

            if ( previousLeaf !== undefined ) {

                // apply state if required.
                prevRenderGraph = previousLeaf._parent;
                prevRenderGraphParent = prevRenderGraph.parent;
                rg = this._parent;

                if ( prevRenderGraphParent !== rg.parent ) {

                    rg.moveStateGraph( state, prevRenderGraphParent, rg.parent );

                    // send state changes and matrix changes to OpenGL.

                    state.applyStateSet( rg.stateset );

                } else if ( rg !== prevRenderGraph ) {

                    // send state changes and matrix changes to OpenGL.
                    state.applyStateSet( rg.stateset );

                }

            } else {

                this._parent.moveStateGraph( state, undefined, this._parent.parent );
                state.applyStateSet( this._parent.stateset );

            }

            this.drawGeometry( state );

        }

    };

    return RenderLeaf;

} );
<commit_msg>Access to program._uniformsCache Map, using static strings. It fixes the optimized too many times warning in Chrome Profiler<commit_after>define( [
    'osg/Matrix',
], function ( Matrix ) {

    'use strict';

    var RenderLeaf = function () {

        this._parent = undefined;
        this._geometry = undefined;
        this._depth = 0.0;

        this._projection = undefined;
        this._view = undefined;
        this._modelWorld = undefined;
        this._modelView = undefined;
    };

    RenderLeaf.prototype = {

        reset: function () {
            this._parent = undefined;
            this._geometry = undefined;
            this._depth = 0.0;

            this._projection = undefined;
            this._view = undefined;
            this._modelWorld = undefined;
            this._modelView = undefined;
        },

        init: function ( parent, geom, projection, view, modelView, modelWorld, depth ) {

            this._parent = parent;
            this._geometry = geom;
            this._depth = depth;

            this._projection = projection;
            this._view = view;
            this._modelWorld = modelWorld;
            this._modelView = modelView;

        },

        drawGeometry: ( function () {

            var tempMatrice = Matrix.create();
            var modelViewUniform, viewUniform, modelWorldUniform, projectionUniform, normalUniform, program;

            return function ( state ) {

                var gl = state.getGraphicContext();

                program = state.getLastProgramApplied();

                modelViewUniform = program._uniformsCache[ 'ModelViewMatrix' ];
                modelWorldUniform = program._uniformsCache[ 'ModelWorldMatrix' ];
                viewUniform = program._uniformsCache[ 'ViewMatrix' ];
                projectionUniform = program._uniformsCache[ 'ProjectionMatrix' ];
                normalUniform = program._uniformsCache[ 'NormalMatrix' ];

                if ( modelViewUniform !== undefined ) {
                    state.modelViewMatrix.set( this._modelView );
                    state.modelViewMatrix.apply( gl, modelViewUniform );
                }

                if ( modelWorldUniform !== undefined ) {
                    state.modelWorldMatrix.set( this._modelWorld );
                    state.modelWorldMatrix.apply( gl, modelWorldUniform );
                }

                if ( viewUniform !== undefined ) {
                    state.viewMatrix.set( this._view );
                    state.viewMatrix.apply( gl, viewUniform );
                }

                if ( projectionUniform !== undefined ) {
                    state.projectionMatrix.set( this._projection );
                    state.projectionMatrix.apply( gl, projectionUniform );
                }

                if ( normalUniform !== undefined ) {

                    // TODO: optimize the uniform scaling case
                    // where inversion is simpler/faster/shared
                    // but profile before
                    Matrix.copy( this._modelView, tempMatrice );
                    var normal = tempMatrice;
                    normal[ 12 ] = 0.0;
                    normal[ 13 ] = 0.0;
                    normal[ 14 ] = 0.0;

                    Matrix.inverse( normal, normal );
                    Matrix.transpose( normal, normal );
                    state.normalMatrix.set( normal );
                    state.normalMatrix.apply( gl, normalUniform );
                }

                this._geometry.drawImplementation( state );

            };
        } )(),

        render: function ( state, previousLeaf ) {

            var prevRenderGraph;
            var prevRenderGraphParent;
            var rg;

            if ( previousLeaf !== undefined ) {

                // apply state if required.
                prevRenderGraph = previousLeaf._parent;
                prevRenderGraphParent = prevRenderGraph.parent;
                rg = this._parent;

                if ( prevRenderGraphParent !== rg.parent ) {

                    rg.moveStateGraph( state, prevRenderGraphParent, rg.parent );

                    // send state changes and matrix changes to OpenGL.

                    state.applyStateSet( rg.stateset );

                } else if ( rg !== prevRenderGraph ) {

                    // send state changes and matrix changes to OpenGL.
                    state.applyStateSet( rg.stateset );

                }

            } else {

                this._parent.moveStateGraph( state, undefined, this._parent.parent );
                state.applyStateSet( this._parent.stateset );

            }

            this.drawGeometry( state );

        }

    };

    return RenderLeaf;

} );
<|endoftext|>


<commit_before>define( [
    'JBrowse/Util'
],
function(Util) {

var c = {
    colorAlignment(feature, score, glyph, track) {
        var strand = feature.get('strand');
        if (Math.abs(strand) != 1 && strand != '+' && strand != '-') {
            return track.colorForBase('reference');
        } else if (track.config.colorByOrientationAndSize) {
            return c.colorByOrientationAndSize.apply(null, arguments)
        } else if (track.config.colorByOrientation) {
            return c.colorByOrientation.apply(null, arguments)
        } else if (track.config.colorBySize) {
            return c.colorByInsertSizePercentile.apply(null, arguments)
        } else if (track.config.useXS) {
            return c.colorByXS.apply(null, arguments)
        } else if (track.config.useTS) {
            return c.colorByTS.apply(null, arguments)
        } else if (track.config.colorByMAPQ) {
            return c.colorByMAPQ.apply(null, arguments)
        } else if (track.config.defaultColor||track.config.useReverseTemplate) {
            if(feature.get('multi_segment_template')) {
                var revflag = feature.get('multi_segment_first');
                if (feature.get('multi_segment_all_correctly_aligned')) {
                    if (revflag || !track.config.useReverseTemplate) {
                        return strand == 1 || strand == '+'
                              ? glyph.getStyle( feature, 'color_fwd_strand' )
                              : glyph.getStyle( feature, 'color_rev_strand' );
                    } else {
                        return strand == 1 || strand == '+'
                            ? glyph.getStyle( feature, 'color_rev_strand' )
                            : glyph.getStyle( feature, 'color_fwd_strand' );
                    }
                }
                if (feature.get('multi_segment_next_segment_unmapped')) {
                    if (revflag || !track.config.useReverseTemplate) {
                        return strand == 1 || strand == '+'
                              ? glyph.getStyle( feature, 'color_fwd_missing_mate' )
                              : glyph.getStyle( feature, 'color_rev_missing_mate' );
                    } else{
                        return strand == 1 || strand == '+'
                              ? glyph.getStyle( feature, 'color_rev_missing_mate' )
                              : glyph.getStyle( feature, 'color_fwd_missing_mate' );
                    }
                }
                if (feature.get('seq_id') == feature.get('next_seq_id')) {
                    if (revflag || !track.config.useReverseTemplate) {
                        return strand == 1 || strand == '+'
                              ? glyph.getStyle( feature, 'color_fwd_strand_not_proper' )
                              : glyph.getStyle( feature, 'color_rev_strand_not_proper' );
                    } else {
                        return strand == 1 || strand == '+'
                              ? glyph.getStyle( feature, 'color_rev_strand_not_proper' )
                              : glyph.getStyle( feature, 'color_fwd_strand_not_proper' );
                    }
                }
                // should only leave aberrant chr
                return strand == 1 || strand == '+'
                        ? glyph.getStyle( feature, 'color_fwd_diff_chr' )
                        : glyph.getStyle( feature, 'color_rev_diff_chr' );
            }
            return strand == 1 || strand == '+'
                  ? glyph.getStyle( feature, 'color_fwd_strand' )
                  : glyph.getStyle( feature, 'color_rev_strand' );
        }
        else return glyph.getStyle( feature, 'color_nostrand' )
    },

    getOrientation(feature, score, glyph, track)  {
        const type = Util.orientationTypes[track.config.orientationType]
        const orientation = type[feature.get('pair_orientation')]
        const map = {
            'LR': 'color_pair_lr',
            'RR': 'color_pair_rr',
            'RL': 'color_pair_rl',
            'LL': 'color_pair_ll'
        }
        return map[orientation]
    },

    colorByOrientation(feature, score, glyph, track)  {
        const p = c.getOrientation.apply(null, arguments)
        return glyph.getStyle(feature, p || 'color_nostrand')
    },

    colorByOrientationAndSize(feature, score, glyph, track)  {
        const p = c.getInsertSizePercentile.apply(null, arguments)
        if(!p) {
            return c.colorByOrientation.apply(null, arguments)
        }
        return glyph.getStyle(feature, p)
    },
    getInsertSizePercentile(feature, score, glyph, track) {
        if (feature.get('is_paired')) {
            const len = Math.abs(feature.get('template_length'))
            if(feature.get('seq_id') != feature.get('next_seq_id')) {
                return 'color_interchrom'
            } else if (track.insertSizeStats.upper < len) {
                return 'color_longinsert'
            } else if (track.insertSizeStats.lower > len) {
                return 'color_shortinsert'
            }
        }
        return null
    },
    colorByInsertSizePercentile(feature, score, glyph, track) {
        const p = c.getInsertSizePercentile.apply(null, arguments)
        return glyph.getStyle(feature, p || 'color_nostrand')
    },

    colorByInsertSize(feature, score, glyph, track) {
        if (feature.get('is_paired') && feature.get('seq_id') != feature.get('next_seq_id')) {
            return glyph.getStyle(feature, 'color_interchrom')
        }
        const s = Math.abs(score/10)
        return `hsl(${s},50%,50%)`;
    },

    colorArcs(feature, score, glyph, track) {
        if (track.config.colorByOrientationAndSize) {
            return c.colorByOrientationAndSize.apply(null, arguments)
        } else if (track.config.colorBySize) {
            return c.colorByInsertSizePercentile.apply(null, arguments)
        } else if (track.config.colorByOrientation) {
            return c.colorByOrientation.apply(null, arguments)
        } else if (track.config.colorByMAPQ) {
            return c.colorByMAPQ.apply(null, arguments)
        } else {
            return c.colorByInsertSize.apply(null, arguments)
        }
    },

    connectorColor(feature, score, glyph, track) {
        if (track.config.colorByOrientation) {
            return c.colorByOrientation.apply(null, arguments)
        } else if(track.config.colorByOrientationAndSize) {
            return c.colorByOrientationAndSize.apply(null, arguments)
        } else {
            return 'black'
        }
    },

    colorByXS(feature, score, glyph, track) {
        const map = {
            '-': 'color_rev_strand',
            '+': 'color_fwd_strand'
        };
        return glyph.getStyle(feature, map[feature.get('xs')] || 'color_nostrand');
    },

    // TS is flipped from XS
    colorByTS(feature, score, glyph, track) {
        const map = {
            '-': feature.get('strand') === -1 ? 'color_fwd_strand' : 'color_rev_strand',
            '+': feature.get('strand') === -1 ? 'color_rev_strand' : 'color_fwd_strand'
        }
        return glyph.getStyle(feature, map[feature.get('ts')] || 'color_nostrand');
    },

    // assumes score cap at 60, which is used by bwa-mem and other tools. some cap at 37
    colorByMAPQ(feature, score, glyph, track) {
        const c = Math.min(feature.get('score') * 4, 200)
        return `rgb(${c},${c},${c})`;
    }
};

return c;

});
<commit_msg>Add check for CRAM tags<commit_after>define( [
    'JBrowse/Util'
],
function(Util) {

var c = {
    colorAlignment(feature, score, glyph, track) {
        var strand = feature.get('strand');
        if (Math.abs(strand) != 1 && strand != '+' && strand != '-') {
            return track.colorForBase('reference');
        } else if (track.config.colorByOrientationAndSize) {
            return c.colorByOrientationAndSize.apply(null, arguments)
        } else if (track.config.colorByOrientation) {
            return c.colorByOrientation.apply(null, arguments)
        } else if (track.config.colorBySize) {
            return c.colorByInsertSizePercentile.apply(null, arguments)
        } else if (track.config.useXS) {
            return c.colorByXS.apply(null, arguments)
        } else if (track.config.useTS) {
            return c.colorByTS.apply(null, arguments)
        } else if (track.config.colorByMAPQ) {
            return c.colorByMAPQ.apply(null, arguments)
        } else if (track.config.defaultColor||track.config.useReverseTemplate) {
            if(feature.get('multi_segment_template')) {
                var revflag = feature.get('multi_segment_first');
                if (feature.get('multi_segment_all_correctly_aligned')) {
                    if (revflag || !track.config.useReverseTemplate) {
                        return strand == 1 || strand == '+'
                              ? glyph.getStyle( feature, 'color_fwd_strand' )
                              : glyph.getStyle( feature, 'color_rev_strand' );
                    } else {
                        return strand == 1 || strand == '+'
                            ? glyph.getStyle( feature, 'color_rev_strand' )
                            : glyph.getStyle( feature, 'color_fwd_strand' );
                    }
                }
                if (feature.get('multi_segment_next_segment_unmapped')) {
                    if (revflag || !track.config.useReverseTemplate) {
                        return strand == 1 || strand == '+'
                              ? glyph.getStyle( feature, 'color_fwd_missing_mate' )
                              : glyph.getStyle( feature, 'color_rev_missing_mate' );
                    } else{
                        return strand == 1 || strand == '+'
                              ? glyph.getStyle( feature, 'color_rev_missing_mate' )
                              : glyph.getStyle( feature, 'color_fwd_missing_mate' );
                    }
                }
                if (feature.get('seq_id') == feature.get('next_seq_id')) {
                    if (revflag || !track.config.useReverseTemplate) {
                        return strand == 1 || strand == '+'
                              ? glyph.getStyle( feature, 'color_fwd_strand_not_proper' )
                              : glyph.getStyle( feature, 'color_rev_strand_not_proper' );
                    } else {
                        return strand == 1 || strand == '+'
                              ? glyph.getStyle( feature, 'color_rev_strand_not_proper' )
                              : glyph.getStyle( feature, 'color_fwd_strand_not_proper' );
                    }
                }
                // should only leave aberrant chr
                return strand == 1 || strand == '+'
                        ? glyph.getStyle( feature, 'color_fwd_diff_chr' )
                        : glyph.getStyle( feature, 'color_rev_diff_chr' );
            }
            return strand == 1 || strand == '+'
                  ? glyph.getStyle( feature, 'color_fwd_strand' )
                  : glyph.getStyle( feature, 'color_rev_strand' );
        }
        else return glyph.getStyle( feature, 'color_nostrand' )
    },

    getOrientation(feature, score, glyph, track)  {
        const type = Util.orientationTypes[track.config.orientationType]
        const orientation = type[feature.get('pair_orientation')]
        const map = {
            'LR': 'color_pair_lr',
            'RR': 'color_pair_rr',
            'RL': 'color_pair_rl',
            'LL': 'color_pair_ll'
        }
        return map[orientation]
    },

    colorByOrientation(feature, score, glyph, track)  {
        const p = c.getOrientation.apply(null, arguments)
        return glyph.getStyle(feature, p || 'color_nostrand')
    },

    colorByOrientationAndSize(feature, score, glyph, track)  {
        const p = c.getInsertSizePercentile.apply(null, arguments)
        if(!p) {
            return c.colorByOrientation.apply(null, arguments)
        }
        return glyph.getStyle(feature, p)
    },
    getInsertSizePercentile(feature, score, glyph, track) {
        if (feature.get('is_paired')) {
            const len = Math.abs(feature.get('template_length'))
            if(feature.get('seq_id') != feature.get('next_seq_id')) {
                return 'color_interchrom'
            } else if (track.insertSizeStats.upper < len) {
                return 'color_longinsert'
            } else if (track.insertSizeStats.lower > len) {
                return 'color_shortinsert'
            }
        }
        return null
    },
    colorByInsertSizePercentile(feature, score, glyph, track) {
        const p = c.getInsertSizePercentile.apply(null, arguments)
        return glyph.getStyle(feature, p || 'color_nostrand')
    },

    colorByInsertSize(feature, score, glyph, track) {
        if (feature.get('is_paired') && feature.get('seq_id') != feature.get('next_seq_id')) {
            return glyph.getStyle(feature, 'color_interchrom')
        }
        const s = Math.abs(score/10)
        return `hsl(${s},50%,50%)`;
    },

    colorArcs(feature, score, glyph, track) {
        if (track.config.colorByOrientationAndSize) {
            return c.colorByOrientationAndSize.apply(null, arguments)
        } else if (track.config.colorBySize) {
            return c.colorByInsertSizePercentile.apply(null, arguments)
        } else if (track.config.colorByOrientation) {
            return c.colorByOrientation.apply(null, arguments)
        } else if (track.config.colorByMAPQ) {
            return c.colorByMAPQ.apply(null, arguments)
        } else {
            return c.colorByInsertSize.apply(null, arguments)
        }
    },

    connectorColor(feature, score, glyph, track) {
        if (track.config.colorByOrientation) {
            return c.colorByOrientation.apply(null, arguments)
        } else if(track.config.colorByOrientationAndSize) {
            return c.colorByOrientationAndSize.apply(null, arguments)
        } else {
            return 'black'
        }
    },

    colorByXS(feature, score, glyph, track) {
        const map = {
            '-': 'color_rev_strand',
            '+': 'color_fwd_strand'
        };
        console.log(feature.get('tags'))
        return glyph.getStyle(feature, map[feature.get('xs')||feature.get('tags').XS] || 'color_nostrand');
    },

    // TS is flipped from XS
    colorByTS(feature, score, glyph, track) {
        const map = {
            '-': feature.get('strand') === -1 ? 'color_fwd_strand' : 'color_rev_strand',
            '+': feature.get('strand') === -1 ? 'color_rev_strand' : 'color_fwd_strand'
        }
        return glyph.getStyle(feature, map[feature.get('ts')||feature.get('tags').TS] || 'color_nostrand');
    },

    // assumes score cap at 60, which is used by bwa-mem and other tools. some cap at 37
    colorByMAPQ(feature, score, glyph, track) {
        const c = Math.min(feature.get('score') * 4, 200)
        return `rgb(${c},${c},${c})`;
    }
};

return c;

});
<|endoftext|>


<commit_before>#!/usr/bin/env node

var manifoldLib = require('manifoldjs-lib'),
    validations = manifoldLib.validations,
    manifestTools = manifoldLib.manifestTools,
    projectBuilder = manifoldLib.projectBuilder,
    projectTools = manifoldLib.projectTools,
    utils = manifoldLib.utils,
    packageTools = manifoldLib.packageTools,
    log = manifoldLib.log,
    url = require('url'),
    path = require('path');

packageTools.checkForUpdate(function (err, updateAvailable) {
  if (!err && updateAvailable) {
    console.log();
    console.log('*******************************************************************************');
    console.log('A new version of ManifoldJS is available (v' + updateAvailable + ').');
    console.log('We recommend that you upgrade.');
    console.log('*******************************************************************************');
    console.log();
  }
});

function checkParameters(program) {
  var unknownArgs = 0;
  if (program.args.length > 0) {
    var command = program.args[0].toLowerCase();
    switch (command) {
      case 'run':
        if (program.args.length < 2) {
          return 'ERROR: You must specify a platform (windows | android).';
        }

        if (!validations.platformToRunValid(program.args[1])) {
          return 'ERROR: Invalid platform specified - [' + program.args[1] + '].';
        }

        unknownArgs = 2;
        program.run = true;
        break;
      case 'package':
        if (program.args.length < 2) {
          return 'ERROR: You must specify a content directory.';
        }

        if (program.args.length < 3) {
          return 'ERROR: You must specify an output package path.';
        }

        unknownArgs = 3;
        program.package = true;
        break;
      case 'visualstudio':
        unknownArgs = 1;
        program.visualstudio = true;
        break;
      default:
        unknownArgs = 1;
        break;
    }
  } else {
    if (!program.manifest) {
      return 'ERROR: You must specify either the web site URL or the location of a W3C manifest (-m | --manifest).';
    }
  }

  if (program.args.length > unknownArgs) {
    return 'ERROR: Unexpected parameters - [' + program.args.slice(unknownArgs).join() + '].';
  }

  // check platforms
  if (program.platforms) {
    var platforms = program.platforms.split(/[\s,]+/);
    if (!validations.platformsValid(platforms)) {
      return 'ERROR: Invalid platform(s) specified.';
    }
  }

  // check log level
  if (program.loglevel) {
    if (!validations.logLevelValid(program.loglevel)) {
      return 'ERROR: Invalid loglevel specified. Valid values are: debug, trace, info, warn, error';
    }
  }
}

function getW3cManifest(siteUrl, manifestLocation, callback) {
  function resolveStartURL(err, manifestInfo) {
    if (err) {
      return callback(err, manifestInfo);
    }

    return manifestTools.validateAndNormalizeStartUrl(siteUrl, manifestInfo, callback);
  }

  if (siteUrl) {
    var parsedSiteUrl = url.parse(siteUrl);
    if (!parsedSiteUrl.hostname) {
      return callback(new Error('The site URL is not a valid URL.'));
    }
  }

  if (manifestLocation) {
    var parsedManifestUrl = url.parse(manifestLocation);
    if (parsedManifestUrl && parsedManifestUrl.host) {
      // download manifest from remote location
      log.info('Downloading manifest from ' + manifestLocation + '...');
      manifestTools.downloadManifestFromUrl(manifestLocation, resolveStartURL);
    } else {
      // read local manifest file
      log.info('Reading manifest file ' + manifestLocation + '...');
      manifestTools.getManifestFromFile(manifestLocation, resolveStartURL);
    }
  } else if (siteUrl) {
    // scan a site to retrieve its manifest
    log.info('Scanning ' + siteUrl + ' for manifest...');
    manifestTools.getManifestFromSite(siteUrl, resolveStartURL);
  } else {
    return callback(new Error('A site URL or manifest should be specified.'));
  }
}

var program = require('commander')
             .usage('<website-url> [options]\n' +
                    '  -or-\n' +
                    '         manifoldjs -m <manifest-location> [options]\n' +
                    '  -or-\n' +
                    '         manifoldjs package <content-directory> <output-package-path>\n' +
                    '  -or-\n' +
                    '         manifoldjs run <windows|android>\n' +
                    '  -or-\n' +
                    '         manifoldjs visualstudio')
             .option('-d, --directory <app-dir>', 'path to the generated project files')
             .option('-s, --shortname <short-name>', 'application short name')
             .option('-l, --loglevel <log-level>', 'debug|info|warn|error', 'warn')
             .option('-p, --platforms <platforms>', '[windows][,windows10][,android][,ios]\n                                    ' +
                                                    '[,chrome][,web][,firefox]', 'windows,windows10,android,ios,chrome,web,firefox')
             .option('-b, --build', 'forces the building process', false)
             .option('-m, --manifest <manifest-location>', 'location of the W3C Web App manifest\n                                    ' +
                                                    'file (URL or local path)')
             .option('-c, --crosswalk', 'enable Crosswalk for Android', false)
             .option('-w, --webAppToolkit', 'adds the Web App Toolkit cordova plugin', false)
             .parse(process.argv);

if (!process.argv.slice(2).length) {
  program.help();
}

var validationResult = checkParameters(program);
if (validationResult) {
  console.log(validationResult);
  process.exit(1);
}

global.logLevel = program.loglevel;
log.setLevel(global.logLevel);

if (program.run) {
  // Run the app for the specified platform

  var platform = program.args[1];
  projectTools.runApp(platform, function (err) {
    if (err) {
      log.error('ERROR: ' + err.message);
    }
  });

} else if (program.visualstudio) {

  projectTools.openVisualStudio(function (err) {
    if (err) {
      log.error('ERROR: ' + err.message);
    } else {
      log.info('The Visual Studio project was opened successfully!');
    }
  });

} else if (program.package) {
  // Creates App Store packages for publishing
  var directory = program.args[1];
  var outputPath = program.args[2];
  var platforms = program.platforms.split(/[\s,]+/);
  projectBuilder.packageApps(platforms, directory, outputPath, function (err) {
    if (err) {
      log.error('ERROR: ' + err.message);
      return;
    }

    log.info('The app store package was created successfully!');
  });
} else {
  var siteUrl = program.args[0];
  var rootDir = program.directory ? path.resolve(program.directory) : process.cwd();
  var platforms = program.platforms.split(/[\s,]+/);

  // remove windows as default platform if run on Linux or MacOS
  // Fix for issue # 115: https://github.com/manifoldjs/ManifoldJS/issues/115
  // it should be removed once cordova adds support for Windows on Linux and MacOS
  if (!utils.isWindows &&
       program.rawArgs.indexOf('-p') === -1 &&
       program.rawArgs.indexOf('--platforms')  === -1) {
    platforms.splice(platforms.indexOf('windows'), 1);
  }

  getW3cManifest(siteUrl, program.manifest, function (err, manifestInfo) {
    if (err) {
      log.error('ERROR: ' + err.message);
      return;
    }

      // Fix #145: don't require a short name
    manifestInfo.content.short_name =   manifestInfo.content.short_name ||
                                        manifestInfo.content.name ||
                                        manifestInfo.default.short_name;

    // if specified as a parameter, override the app's short name
    if (program.shortname) {
      manifestInfo.content.short_name = program.shortname;
    }

    log.debug('Manifest contents:');
    log.debug(JSON.stringify(manifestInfo.content, null, 4));

    // add generatedFrom value to manifestInfo for telemetry
    manifestInfo.generatedFrom = 'CLI';

    // Create the apps for the specified platforms
    projectBuilder.createApps(manifestInfo, rootDir, platforms, program, function (err) {
      if (err) {
        var errmsg = err.message;
        if (global.logLevel !== 'debug') {
          errmsg += ' For more information, run manifoldjs with the diagnostics level set to debug (e.g. manifoldjs [...] -l debug)';
        }

        log.error(errmsg);
        return;
      }

      log.info('The application(s) are ready!');
    });
  });
}
<commit_msg>Removed output package path command line parameter for package command -- it's determined automatically<commit_after>#!/usr/bin/env node

var manifoldLib = require('manifoldjs-lib'),
    validations = manifoldLib.validations,
    manifestTools = manifoldLib.manifestTools,
    projectBuilder = manifoldLib.projectBuilder,
    projectTools = manifoldLib.projectTools,
    utils = manifoldLib.utils,
    packageTools = manifoldLib.packageTools,
    log = manifoldLib.log,
    url = require('url'),
    path = require('path');

packageTools.checkForUpdate(function (err, updateAvailable) {
  if (!err && updateAvailable) {
    console.log();
    console.log('*******************************************************************************');
    console.log('A new version of ManifoldJS is available (v' + updateAvailable + ').');
    console.log('We recommend that you upgrade.');
    console.log('*******************************************************************************');
    console.log();
  }
});

function checkParameters(program) {
  var unknownArgs = 0;
  if (program.args.length > 0) {
    var command = program.args[0].toLowerCase();
    switch (command) {
      case 'run':
        if (program.args.length < 2) {
          return 'ERROR: You must specify a platform (windows | android).';
        }

        if (!validations.platformToRunValid(program.args[1])) {
          return 'ERROR: Invalid platform specified - [' + program.args[1] + '].';
        }

        unknownArgs = 2;
        program.run = true;
        break;
      case 'package':
        if (program.args.length < 2) {
          return 'ERROR: You must specify a content directory.';
        }

        unknownArgs = 2;
        program.package = true;
        break;
      case 'visualstudio':
        unknownArgs = 1;
        program.visualstudio = true;
        break;
      default:
        unknownArgs = 1;
        break;
    }
  } else {
    if (!program.manifest) {
      return 'ERROR: You must specify either the web site URL or the location of a W3C manifest (-m | --manifest).';
    }
  }

  if (program.args.length > unknownArgs) {
    return 'ERROR: Unexpected parameters - [' + program.args.slice(unknownArgs).join() + '].';
  }

  // check platforms
  if (program.platforms) {
    var platforms = program.platforms.split(/[\s,]+/);
    if (!validations.platformsValid(platforms)) {
      return 'ERROR: Invalid platform(s) specified.';
    }
  }

  // check log level
  if (program.loglevel) {
    if (!validations.logLevelValid(program.loglevel)) {
      return 'ERROR: Invalid loglevel specified. Valid values are: debug, trace, info, warn, error';
    }
  }
}

function getW3cManifest(siteUrl, manifestLocation, callback) {
  function resolveStartURL(err, manifestInfo) {
    if (err) {
      return callback(err, manifestInfo);
    }

    return manifestTools.validateAndNormalizeStartUrl(siteUrl, manifestInfo, callback);
  }

  if (siteUrl) {
    var parsedSiteUrl = url.parse(siteUrl);
    if (!parsedSiteUrl.hostname) {
      return callback(new Error('The site URL is not a valid URL.'));
    }
  }

  if (manifestLocation) {
    var parsedManifestUrl = url.parse(manifestLocation);
    if (parsedManifestUrl && parsedManifestUrl.host) {
      // download manifest from remote location
      log.info('Downloading manifest from ' + manifestLocation + '...');
      manifestTools.downloadManifestFromUrl(manifestLocation, resolveStartURL);
    } else {
      // read local manifest file
      log.info('Reading manifest file ' + manifestLocation + '...');
      manifestTools.getManifestFromFile(manifestLocation, resolveStartURL);
    }
  } else if (siteUrl) {
    // scan a site to retrieve its manifest
    log.info('Scanning ' + siteUrl + ' for manifest...');
    manifestTools.getManifestFromSite(siteUrl, resolveStartURL);
  } else {
    return callback(new Error('A site URL or manifest should be specified.'));
  }
}

var program = require('commander')
             .usage('<website-url> [options]\n' +
                    '  -or-\n' +
                    '         manifoldjs -m <manifest-location> [options]\n' +
                    '  -or-\n' +
                    '         manifoldjs package <app-directory>\n' +
                    '  -or-\n' +
                    '         manifoldjs run <windows|android>\n' +
                    '  -or-\n' +
                    '         manifoldjs visualstudio')
             .option('-d, --directory <app-dir>', 'path to the generated project files')
             .option('-s, --shortname <short-name>', 'application short name')
             .option('-l, --loglevel <log-level>', 'debug|info|warn|error', 'warn')
             .option('-p, --platforms <platforms>', '[windows][,windows10][,android][,ios]\n                                    ' +
                                                    '[,chrome][,web][,firefox]', 'windows,windows10,android,ios,chrome,web,firefox')
             .option('-b, --build', 'forces the building process', false)
             .option('-m, --manifest <manifest-location>', 'location of the W3C Web App manifest\n                                    ' +
                                                    'file (URL or local path)')
             .option('-c, --crosswalk', 'enable Crosswalk for Android', false)
             .option('-w, --webAppToolkit', 'adds the Web App Toolkit cordova plugin', false)
             .parse(process.argv);

if (!process.argv.slice(2).length) {
  program.help();
}

var validationResult = checkParameters(program);
if (validationResult) {
  console.log(validationResult);
  process.exit(1);
}

global.logLevel = program.loglevel;
log.setLevel(global.logLevel);

if (program.run) {
  // Run the app for the specified platform

  var platform = program.args[1];
  projectTools.runApp(platform, function (err) {
    if (err) {
      log.error('ERROR: ' + err.message);
    }
  });

} else if (program.visualstudio) {

  projectTools.openVisualStudio(function (err) {
    if (err) {
      log.error('ERROR: ' + err.message);
    } else {
      log.info('The Visual Studio project was opened successfully!');
    }
  });

} else if (program.package) {
  // Creates App Store packages for publishing
  var directory = program.args[1];
  var platforms = program.platforms.split(/[\s,]+/);
  projectBuilder.packageApps(platforms, directory, function (err) {
    if (err) {
      log.error('ERROR: ' + err.message);
      return;
    }

    log.info('The app store package was created successfully!');
  });
} else {
  var siteUrl = program.args[0];
  var rootDir = program.directory ? path.resolve(program.directory) : process.cwd();
  var platforms = program.platforms.split(/[\s,]+/);

  // remove windows as default platform if run on Linux or MacOS
  // Fix for issue # 115: https://github.com/manifoldjs/ManifoldJS/issues/115
  // it should be removed once cordova adds support for Windows on Linux and MacOS
  if (!utils.isWindows &&
       program.rawArgs.indexOf('-p') === -1 &&
       program.rawArgs.indexOf('--platforms')  === -1) {
    platforms.splice(platforms.indexOf('windows'), 1);
  }

  getW3cManifest(siteUrl, program.manifest, function (err, manifestInfo) {
    if (err) {
      log.error('ERROR: ' + err.message);
      return;
    }

      // Fix #145: don't require a short name
    manifestInfo.content.short_name =   manifestInfo.content.short_name ||
                                        manifestInfo.content.name ||
                                        manifestInfo.default.short_name;

    // if specified as a parameter, override the app's short name
    if (program.shortname) {
      manifestInfo.content.short_name = program.shortname;
    }

    log.debug('Manifest contents:');
    log.debug(JSON.stringify(manifestInfo.content, null, 4));

    // add generatedFrom value to manifestInfo for telemetry
    manifestInfo.generatedFrom = 'CLI';

    // Create the apps for the specified platforms
    projectBuilder.createApps(manifestInfo, rootDir, platforms, program, function (err) {
      if (err) {
        var errmsg = err.message;
        if (global.logLevel !== 'debug') {
          errmsg += ' For more information, run manifoldjs with the diagnostics level set to debug (e.g. manifoldjs [...] -l debug)';
        }

        log.error(errmsg);
        return;
      }

      log.info('The application(s) are ready!');
    });
  });
}
<|endoftext|>