<commit_before>import Renderer from "./renderer.js";
import {SVG} from "../../core/svg.js";
import FeatureBinarySearchTree from "../feature-binary-search-tree.js";
import GenomeBrowserUtils from "../genome-browser-utils.js";
import GenomeBrowserConstants from "../genome-browser-constants.js";

export default class AlignmentRenderer extends Renderer {

    render(data, options) {
        // Get data to render
        const coverage = data[0] || []; // Coverage data is in the first position
        const alignments = data[1] || null; // Alignments data is in the second position (if provided)

        // Define the height of the coverage track
        const regionSize = options.requestedRegion.end - options.requestedRegion.start + 1;
        const parentHeight = options.svgCanvasFeatures.parentElement.clientHeight;
        const coverageHeight = regionSize < this.config.alignmentsMaxRegionSize ? 50 : parentHeight;

        const coverageParent = SVG.addChild(options.svgCanvasFeatures, "g", {});
        const alignmentsParent = SVG.addChild(options.svgCanvasFeatures, "g", {});

        // Render coverage data
        coverage.forEach(item => {
            this.#renderCoverage(coverageParent, item, coverageHeight, options);
        });

        // Group and render alignments
        if (alignments && regionSize < this.config.alignmentsMaxRegionSize) {
            this.#groupAlignments(alignments, options).forEach(group => {
                this.#renderAlignments(alignmentsParent, group, coverageHeight, options);
            });
        }
    }

    #groupAlignments(reads, options) {
        const groups = {};

        // Filter reads to avoid duplications
        const nonDuplicatedReads = reads.filter(read => {
            return !options.renderedFeatures.has(read.id);
        });

        // Group only non duplicated reads
        nonDuplicatedReads.forEach(read => {
            const id = read.id;
            if (!groups[id]) {
                groups[id] = [read];
            } else {
                const newReadPosition = read.alignment.position.position;
                const storedReadPosition = groups[id][0].alignment.position.position;

                // FIXME: For some reason, the webservice is some times returning the exactly same read more than once.
                if (newReadPosition !== storedReadPosition) {
                    // Order the alignments to be rendered properly
                    if (newReadPosition > storedReadPosition) {
                        groups[id].push(read);
                    } else {
                        groups[id].unshift(read);
                    }
                }
            }

            // Mark this read as rendered
            options.renderedFeatures.add(read.id);
        });

        return Object.values(groups);
    }

    #analyseRead(feature) {
        const differences = [];
        const start = feature.alignment.position.position;

        const cigar = [];
        let relativePosition = 0;
        const insertions = [];

        (feature.alignment.cigar || []).forEach(item => {
            cigar.push(item.operationLength);
            const length = parseInt(item.operationLength);
            switch (item.operation) {
                case "CLIP_SOFT":
                    cigar.push("S");
                    break;
                case "ALIGNMENT_MATCH":
                    cigar.push("M");
                    relativePosition = relativePosition + length;
                    break;
                case "INSERT":
                    cigar.push("I");

                    // We put this here because it will be read to calculate the position of the mismatches
                    insertions.push({
                        pos: relativePosition,
                        length: length,
                    });
                    differences.push({
                        pos: relativePosition,
                        seq: feature.alignedSequence.slice(relativePosition, relativePosition + length),
                        op: "I",
                        length: length,
                    });
                    break;
                case "DELETE":
                    cigar.push("D");
                    differences.push({
                        pos: relativePosition,
                        op: "D",
                        length: length,
                    });
                    relativePosition = relativePosition + length;
                    break;
                case "SKIP":
                    cigar.push("N");
                    relativePosition = relativePosition + length;
                    break;
                default:
            }
        });

        // Assign feature information (cigar, start and end positions)
        Object.assign(feature, {
            cigar: cigar.join(""),
            start: start,
            end: start + relativePosition - 1,
        });

        if (feature.info?.["MD"]) {
            const md = feature.info["MD"][1];
            const matches = md.match(/([0-9]+)|([^0-9]+)/g);
            let position = 0;

            if (feature.alignment.cigar[0].operation === "CLIP_SOFT") {
                position = parseInt(feature.alignment.cigar[0].operationLength);
            }

            // This variable will contain the offset between the insertion and the position where the mismatch is
            // Imagine we have this sequence ACTGCT, and we have an insertion in position 3 and a mismatch in position 5
            // The mismatch will be in position 5 of the sequence, but located in position 4 when showed relative to the
            // reference genome.
            let offset = position;
            for (let i = 0; i < matches.length; i++) {
                if (i % 2 === 0) {
                    // Number
                    position += parseInt(matches[i]);
                } else {
                    if (insertions.length > 0) {
                        for (let j = 0; j < insertions.length; j++) {
                            if (insertions[j].pos < position) {
                                position += insertions[j].length;
                                offset += insertions[j].length;
                                insertions[j].pos = Infinity;
                            } else {
                                break;
                            }
                        }
                    }

                    // Not deletion
                    if (matches[i][0] !== "^") {
                        // Reference nucleotide
                        if (matches[i] === feature.alignedSequence[position]) {
                            console.error("Something strange happened. The mismatch matches the nucleotide of the reference genome?");
                        }

                        differences.push({
                            pos: position - offset,
                            seq: feature.alignedSequence[position],
                            quality: feature.alignedQuality?.[position] || 0,
                            op: "M",
                            length: 1,
                        });

                        position += 1;
                    } else {
                        // -1 because we should not count the ^
                        offset -= matches[i].length - 1;
                    }
                }
            }
        }

        return [differences, relativePosition];
    }

    #renderCoverage(group, coverage, height, options) {
        const start = parseInt(coverage.start);
        const end = parseInt(coverage.end);
        const pixelWidth = (end - start + 1) * options.pixelBase;
        const maximumValue = coverage.stats.max; // Math.max.apply(null, coverageList);
        const points = [];

        const startPoint = GenomeBrowserUtils.getFeatureX(start, options);
        const endPoint = GenomeBrowserUtils.getFeatureX(end, options);

        // First starting point
        points.push(`${startPoint},${height}`);

        if (maximumValue > 0) {
            const maxValueRatio = height / maximumValue;
            let prevCoverage = -1;
            let prevPosition = -1;

            (coverage.values || []).forEach((value, index) => {
                const pos = index * coverage.windowSize;

                if (value !== prevCoverage || index === coverage.values.length - 1) {
                    if (prevCoverage > -1 && prevPosition + 1 < index) {
                        // We need to add the previous position as well to make a flat line between positions with equal coverage
                        const prevX = GenomeBrowserUtils.getFeatureX(start + prevPosition * coverage.windowSize, options);
                        const prevY = height - (prevCoverage * maxValueRatio);
                        points.push(`${prevX},${prevY}`);
                    }
                    prevCoverage = value;
                    prevPosition = index;

                    const x = GenomeBrowserUtils.getFeatureX(start + pos, options);
                    const y = height - (value * maxValueRatio);
                    points.push(`${x},${y}`);
                }
            });
        }

        // Last point
        points.push(`${endPoint},${height}`);

        SVG.addChild(group, "polyline", {
            points: points.join(" "),
            stroke: "lightgrey",
            fill: "lightgrey",
            width: pixelWidth,
            height: height,
            cursor: "pointer",
        });
    }

    #renderDifferences(parent, differences, readStart, readHeight, rowY, options) {
        (differences || []).forEach(diff => {
            const start = GenomeBrowserUtils.getFeatureX(diff.pos + readStart, options);

            if (diff.op === "M") {
                const end = start + options.pixelBase;
                const color = GenomeBrowserConstants.SEQUENCE_COLORS[diff.seq];
                SVG.addChild(parent, "path", {
                    "d": `M${start} ${rowY} V${rowY + readHeight} H${end} V${rowY} H${start}`,
                    "stroke": color,
                    "stroke-width": 0.9,
                    "fill": color,
                    "fill-opacity": diff.quality < this.config.minMappingQuality ? 0.3 : 0.7,
                });
            } else if (diff.op === "I") {
                const text = SVG.addChild(parent, "text", {
                    y: rowY + readHeight,
                    x: start - (options.pixelBase / 2),
                    class: "ocb-font-ubuntumono ocb-font-size-15",
                    // textLength: diff.size,
                });
                text.textContent = "|";
            } else if (diff.op === "D") {
                const end = start + options.pixelBase * diff.length;
                SVG.addChild(parent, "path", {
                    "d": `M${start} ${rowY + (readHeight / 2)} H${end} H${start}`,
                    "stroke": "#000",
                    "stroke-width": 0.7,
                    "fill": "#000",
                    "fill-opacity": 0.5,
                });
            }
        });
    }

    #renderAlignments(parent, alignments, startHeight, options) {
        const differences = [];
        const starts = [];
        const ends = [];

        alignments.forEach(read => {
            const [diffs, length] = this.#analyseRead(read, options);
            differences.push(diffs);
            starts.push(read.alignment.position.position);
            ends.push(read.alignment.position.position + length - 1);
        });

        // transform to pixel position
        const alignmentStart = GenomeBrowserUtils.getFeatureX(Math.min.apply(null, starts), options);
        const alignmentEnd = GenomeBrowserUtils.getFeatureX(Math.max.apply(null, ends), options);

        const height = this.getValueFromConfig("height", []);
        const rowHeight = height + 5;
        let rowY = startHeight + 10;
        let fitted = false;

        while (!fitted) {
            if (!options.renderedArea[rowY]) {
                // eslint-disable-next-line no-param-reassign
                options.renderedArea[rowY] = new FeatureBinarySearchTree();
            }

            const foundRow = options.renderedArea[rowY].add({
                start: alignmentStart,
                end: alignmentEnd,
            });

            if (foundRow) {
                let prevAlignmentEnd = null;
                const connectorsPoints = [];

                alignments.forEach((read, index) => {
                    const strand = this.getValueFromConfig("strand", [read]) || "Forward";
                    const start = GenomeBrowserUtils.getFeatureX(starts[index], options);
                    const end = GenomeBrowserUtils.getFeatureX(ends[index], options);
                    let points = []; // To save read points

                    if (strand.toUpperCase() === "REVERSE") {
                        points = [
                            `M${start},${rowY + (height / 2)}`,
                            `L${start + 5},${rowY}`,
                            `H${end}`,
                            `V${rowY + height}`,
                            `H${start + 5}`,
                            "Z",
                        ];
                    } else {
                        points = [
                            `M${start},${rowY}`,
                            `H${end - 5}`,
                            `L${end},${rowY + (height / 2)}`,
                            `L${end - 5},${rowY + height}`,
                            `H${start}`,
                            "Z",
                        ];
                    }

                    // Get read attributes
                    const readColor = this.getValueFromConfig("color", [read, alignments, this.config.minMappingQuality]);
                    const readOpacity = this.getValueFromConfig("opacity", [read, this.config.minMappingQuality]);

                    // Render this read
                    const readElement = SVG.addChild(parent, "path", {
                        "d": points.join(" "),
                        "stroke": "black",
                        "stroke-width": 0.5,
                        "fill": readColor,
                        "fill-opacity": readOpacity,
                        "cursor": "pointer",
                    });

                    // Display read tooltip
                    $(readElement).qtip({
                        content: {
                            title: this.getValueFromConfig("tooltipTitle", [read]),
                            text: this.getValueFromConfig("tooltipText", [read]),
                        },
                        position: {
                            viewport: $(window),
                            target: "mouse",
                            adjust: {x: 10, y: 10},
                        },
                        style: {
                            width: true,
                            classes: `${this.config.toolTipfontClass} ui-tooltip ui-tooltip-shadow`,
                        },
                    });

                    // Render differences
                    if (options.regionSize < 1000) {
                        this.#renderDifferences(parent, differences[index], starts[index], height, rowY, options);
                    }

                    // Check for rendering a line connecting reads
                    // NOTE: the second condition is to avoud drawing the connector in overlapped reads
                    if (prevAlignmentEnd && prevAlignmentEnd < start) {
                        connectorsPoints.push(`M${prevAlignmentEnd} ${rowY + (height / 2)} H${start}`);
                    }
                    prevAlignmentEnd = end;
                });

                // Check for rendering connectors points
                if (connectorsPoints.length > 0) {
                    SVG.addChild(parent, "path", {
                        "d": connectorsPoints.join(" "),
                        "stroke": "black",
                        "stroke-width": 0.5,
                        "fill": "transparent",
                    });
                }

                // Read fittend
                fitted = true;
            }
            rowY = rowY + rowHeight;
        }
    }

    getDefaultConfig() {
        return {
            alignmentsMaxRegionSize: 50000,
            minMappingQuality: 20, // Reads with a mapping quality under 20 will have a transparency
            strand: GenomeBrowserUtils.alignmentStrandParser,
            color: GenomeBrowserUtils.alignmentColorFormatter,
            opacity: (read, minMappingQuality) => {
                return read.alignment.mappingQuality > minMappingQuality ? 0.7 : 0.3;
            },
            height: 10,
            tooltipTitle: GenomeBrowserUtils.alignmentTooltipTitleFormatter,
            tooltipText: GenomeBrowserUtils.alignmentTooltipTextFormatter,
        };
    }

}
<commit_msg>gb: use alignment opacity formatter in alignment renderer #TASK-855<commit_after>import Renderer from "./renderer.js";
import {SVG} from "../../core/svg.js";
import FeatureBinarySearchTree from "../feature-binary-search-tree.js";
import GenomeBrowserUtils from "../genome-browser-utils.js";
import GenomeBrowserConstants from "../genome-browser-constants.js";

export default class AlignmentRenderer extends Renderer {

    render(data, options) {
        // Get data to render
        const coverage = data[0] || []; // Coverage data is in the first position
        const alignments = data[1] || null; // Alignments data is in the second position (if provided)

        // Define the height of the coverage track
        const regionSize = options.requestedRegion.end - options.requestedRegion.start + 1;
        const parentHeight = options.svgCanvasFeatures.parentElement.clientHeight;
        const coverageHeight = regionSize < this.config.alignmentsMaxRegionSize ? 50 : parentHeight;

        const coverageParent = SVG.addChild(options.svgCanvasFeatures, "g", {});
        const alignmentsParent = SVG.addChild(options.svgCanvasFeatures, "g", {});

        // Render coverage data
        coverage.forEach(item => {
            this.#renderCoverage(coverageParent, item, coverageHeight, options);
        });

        // Group and render alignments
        if (alignments && regionSize < this.config.alignmentsMaxRegionSize) {
            this.#groupAlignments(alignments, options).forEach(group => {
                this.#renderAlignments(alignmentsParent, group, coverageHeight, options);
            });
        }
    }

    #groupAlignments(reads, options) {
        const groups = {};

        // Filter reads to avoid duplications
        const nonDuplicatedReads = reads.filter(read => {
            return !options.renderedFeatures.has(read.id);
        });

        // Group only non duplicated reads
        nonDuplicatedReads.forEach(read => {
            const id = read.id;
            if (!groups[id]) {
                groups[id] = [read];
            } else {
                const newReadPosition = read.alignment.position.position;
                const storedReadPosition = groups[id][0].alignment.position.position;

                // FIXME: For some reason, the webservice is some times returning the exactly same read more than once.
                if (newReadPosition !== storedReadPosition) {
                    // Order the alignments to be rendered properly
                    if (newReadPosition > storedReadPosition) {
                        groups[id].push(read);
                    } else {
                        groups[id].unshift(read);
                    }
                }
            }

            // Mark this read as rendered
            options.renderedFeatures.add(read.id);
        });

        return Object.values(groups);
    }

    #analyseRead(feature) {
        const differences = [];
        const start = feature.alignment.position.position;

        const cigar = [];
        let relativePosition = 0;
        const insertions = [];

        (feature.alignment.cigar || []).forEach(item => {
            cigar.push(item.operationLength);
            const length = parseInt(item.operationLength);
            switch (item.operation) {
                case "CLIP_SOFT":
                    cigar.push("S");
                    break;
                case "ALIGNMENT_MATCH":
                    cigar.push("M");
                    relativePosition = relativePosition + length;
                    break;
                case "INSERT":
                    cigar.push("I");

                    // We put this here because it will be read to calculate the position of the mismatches
                    insertions.push({
                        pos: relativePosition,
                        length: length,
                    });
                    differences.push({
                        pos: relativePosition,
                        seq: feature.alignedSequence.slice(relativePosition, relativePosition + length),
                        op: "I",
                        length: length,
                    });
                    break;
                case "DELETE":
                    cigar.push("D");
                    differences.push({
                        pos: relativePosition,
                        op: "D",
                        length: length,
                    });
                    relativePosition = relativePosition + length;
                    break;
                case "SKIP":
                    cigar.push("N");
                    relativePosition = relativePosition + length;
                    break;
                default:
            }
        });

        // Assign feature information (cigar, start and end positions)
        Object.assign(feature, {
            cigar: cigar.join(""),
            start: start,
            end: start + relativePosition - 1,
        });

        if (feature.info?.["MD"]) {
            const md = feature.info["MD"][1];
            const matches = md.match(/([0-9]+)|([^0-9]+)/g);
            let position = 0;

            if (feature.alignment.cigar[0].operation === "CLIP_SOFT") {
                position = parseInt(feature.alignment.cigar[0].operationLength);
            }

            // This variable will contain the offset between the insertion and the position where the mismatch is
            // Imagine we have this sequence ACTGCT, and we have an insertion in position 3 and a mismatch in position 5
            // The mismatch will be in position 5 of the sequence, but located in position 4 when showed relative to the
            // reference genome.
            let offset = position;
            for (let i = 0; i < matches.length; i++) {
                if (i % 2 === 0) {
                    // Number
                    position += parseInt(matches[i]);
                } else {
                    if (insertions.length > 0) {
                        for (let j = 0; j < insertions.length; j++) {
                            if (insertions[j].pos < position) {
                                position += insertions[j].length;
                                offset += insertions[j].length;
                                insertions[j].pos = Infinity;
                            } else {
                                break;
                            }
                        }
                    }

                    // Not deletion
                    if (matches[i][0] !== "^") {
                        // Reference nucleotide
                        if (matches[i] === feature.alignedSequence[position]) {
                            console.error("Something strange happened. The mismatch matches the nucleotide of the reference genome?");
                        }

                        differences.push({
                            pos: position - offset,
                            seq: feature.alignedSequence[position],
                            quality: feature.alignedQuality?.[position] || 0,
                            op: "M",
                            length: 1,
                        });

                        position += 1;
                    } else {
                        // -1 because we should not count the ^
                        offset -= matches[i].length - 1;
                    }
                }
            }
        }

        return [differences, relativePosition];
    }

    #renderCoverage(group, coverage, height, options) {
        const start = parseInt(coverage.start);
        const end = parseInt(coverage.end);
        const pixelWidth = (end - start + 1) * options.pixelBase;
        const maximumValue = coverage.stats.max; // Math.max.apply(null, coverageList);
        const points = [];

        const startPoint = GenomeBrowserUtils.getFeatureX(start, options);
        const endPoint = GenomeBrowserUtils.getFeatureX(end, options);

        // First starting point
        points.push(`${startPoint},${height}`);

        if (maximumValue > 0) {
            const maxValueRatio = height / maximumValue;
            let prevCoverage = -1;
            let prevPosition = -1;

            (coverage.values || []).forEach((value, index) => {
                const pos = index * coverage.windowSize;

                if (value !== prevCoverage || index === coverage.values.length - 1) {
                    if (prevCoverage > -1 && prevPosition + 1 < index) {
                        // We need to add the previous position as well to make a flat line between positions with equal coverage
                        const prevX = GenomeBrowserUtils.getFeatureX(start + prevPosition * coverage.windowSize, options);
                        const prevY = height - (prevCoverage * maxValueRatio);
                        points.push(`${prevX},${prevY}`);
                    }
                    prevCoverage = value;
                    prevPosition = index;

                    const x = GenomeBrowserUtils.getFeatureX(start + pos, options);
                    const y = height - (value * maxValueRatio);
                    points.push(`${x},${y}`);
                }
            });
        }

        // Last point
        points.push(`${endPoint},${height}`);

        SVG.addChild(group, "polyline", {
            points: points.join(" "),
            stroke: "lightgrey",
            fill: "lightgrey",
            width: pixelWidth,
            height: height,
            cursor: "pointer",
        });
    }

    #renderDifferences(parent, differences, readStart, readHeight, rowY, options) {
        (differences || []).forEach(diff => {
            const start = GenomeBrowserUtils.getFeatureX(diff.pos + readStart, options);

            if (diff.op === "M") {
                const end = start + options.pixelBase;
                const color = GenomeBrowserConstants.SEQUENCE_COLORS[diff.seq];
                SVG.addChild(parent, "path", {
                    "d": `M${start} ${rowY} V${rowY + readHeight} H${end} V${rowY} H${start}`,
                    "stroke": color,
                    "stroke-width": 0.9,
                    "fill": color,
                    "fill-opacity": diff.quality < this.config.minMappingQuality ? 0.3 : 0.7,
                });
            } else if (diff.op === "I") {
                const text = SVG.addChild(parent, "text", {
                    y: rowY + readHeight,
                    x: start - (options.pixelBase / 2),
                    class: "ocb-font-ubuntumono ocb-font-size-15",
                    // textLength: diff.size,
                });
                text.textContent = "|";
            } else if (diff.op === "D") {
                const end = start + options.pixelBase * diff.length;
                SVG.addChild(parent, "path", {
                    "d": `M${start} ${rowY + (readHeight / 2)} H${end} H${start}`,
                    "stroke": "#000",
                    "stroke-width": 0.7,
                    "fill": "#000",
                    "fill-opacity": 0.5,
                });
            }
        });
    }

    #renderAlignments(parent, alignments, startHeight, options) {
        const differences = [];
        const starts = [];
        const ends = [];

        alignments.forEach(read => {
            const [diffs, length] = this.#analyseRead(read, options);
            differences.push(diffs);
            starts.push(read.alignment.position.position);
            ends.push(read.alignment.position.position + length - 1);
        });

        // transform to pixel position
        const alignmentStart = GenomeBrowserUtils.getFeatureX(Math.min.apply(null, starts), options);
        const alignmentEnd = GenomeBrowserUtils.getFeatureX(Math.max.apply(null, ends), options);

        const height = this.getValueFromConfig("height", []);
        const rowHeight = height + 5;
        let rowY = startHeight + 10;
        let fitted = false;

        while (!fitted) {
            if (!options.renderedArea[rowY]) {
                // eslint-disable-next-line no-param-reassign
                options.renderedArea[rowY] = new FeatureBinarySearchTree();
            }

            const foundRow = options.renderedArea[rowY].add({
                start: alignmentStart,
                end: alignmentEnd,
            });

            if (foundRow) {
                let prevAlignmentEnd = null;
                const connectorsPoints = [];

                alignments.forEach((read, index) => {
                    const strand = this.getValueFromConfig("strand", [read]) || "Forward";
                    const start = GenomeBrowserUtils.getFeatureX(starts[index], options);
                    const end = GenomeBrowserUtils.getFeatureX(ends[index], options);
                    let points = []; // To save read points

                    if (strand.toUpperCase() === "REVERSE") {
                        points = [
                            `M${start},${rowY + (height / 2)}`,
                            `L${start + 5},${rowY}`,
                            `H${end}`,
                            `V${rowY + height}`,
                            `H${start + 5}`,
                            "Z",
                        ];
                    } else {
                        points = [
                            `M${start},${rowY}`,
                            `H${end - 5}`,
                            `L${end},${rowY + (height / 2)}`,
                            `L${end - 5},${rowY + height}`,
                            `H${start}`,
                            "Z",
                        ];
                    }

                    // Get read attributes
                    const readColor = this.getValueFromConfig("color", [read, alignments, this.config.minMappingQuality]);
                    const readOpacity = this.getValueFromConfig("opacity", [read, alignments, this.config.minMappingQuality]);

                    // Render this read
                    const readElement = SVG.addChild(parent, "path", {
                        "d": points.join(" "),
                        "stroke": "black",
                        "stroke-width": 0.5,
                        "fill": readColor,
                        "fill-opacity": readOpacity,
                        "cursor": "pointer",
                    });

                    // Display read tooltip
                    $(readElement).qtip({
                        content: {
                            title: this.getValueFromConfig("tooltipTitle", [read]),
                            text: this.getValueFromConfig("tooltipText", [read]),
                        },
                        position: {
                            viewport: $(window),
                            target: "mouse",
                            adjust: {x: 10, y: 10},
                        },
                        style: {
                            width: true,
                            classes: `${this.config.toolTipfontClass} ui-tooltip ui-tooltip-shadow`,
                        },
                    });

                    // Render differences
                    if (options.regionSize < 1000) {
                        this.#renderDifferences(parent, differences[index], starts[index], height, rowY, options);
                    }

                    // Check for rendering a line connecting reads
                    // NOTE: the second condition is to avoud drawing the connector in overlapped reads
                    if (prevAlignmentEnd && prevAlignmentEnd < start) {
                        connectorsPoints.push(`M${prevAlignmentEnd} ${rowY + (height / 2)} H${start}`);
                    }
                    prevAlignmentEnd = end;
                });

                // Check for rendering connectors points
                if (connectorsPoints.length > 0) {
                    SVG.addChild(parent, "path", {
                        "d": connectorsPoints.join(" "),
                        "stroke": "black",
                        "stroke-width": 0.5,
                        "fill": "transparent",
                    });
                }

                // Read fittend
                fitted = true;
            }
            rowY = rowY + rowHeight;
        }
    }

    getDefaultConfig() {
        return {
            alignmentsMaxRegionSize: 50000,
            minMappingQuality: 20, // Reads with a mapping quality under 20 will have a transparency
            height: 10,
            strand: GenomeBrowserUtils.alignmentStrandParser,
            color: GenomeBrowserUtils.alignmentColorFormatter,
            opacity: GenomeBrowserUtils.alignmentOpacityFormatter,
            tooltipTitle: GenomeBrowserUtils.alignmentTooltipTitleFormatter,
            tooltipText: GenomeBrowserUtils.alignmentTooltipTextFormatter,
        };
    }

}
<|endoftext|>


<commit_before>(function j(h, d, e) {
  function f(a, b) {
    if (!d[a]) {
      if (!h[a]) {
        var g = "function" == typeof require && require;
        if (!b && g) {
          return g(a, !0);
        }
        if (c) {
          return c(a, !0);
        }
        g = Error("Cannot find module '" + a + "'");
        throw g.code = "MODULE_NOT_FOUND", g;
      }
      g = d[a] = {b:{}};
      h[a][0].call(g.b, function(b) {
        var c = h[a][1][b];
        return f(c ? c : b);
      }, g, g.b, j, h, d, e);
    }
    return d[a].b;
  }
  for (var c = "function" == typeof require && require, b = 0;b < e.length;b++) {
    f(e[b]);
  }
  return f;
})({1:[function(i, h) {
  h.b = function(d) {
    function e(a, b) {
      b && f.push({name:"meta", a:{name:a, content:!0 === b ? "yes" : b}});
    }
    var f = [], c = -1 !== ["standalone", "fullscreen"].indexOf(d.display);
    e("apple-mobile-web-app-capable", c);
    e("mobile-web-app-capable", c);
    e("apple-mobile-web-app-title", d.short_name || d.name);
    e("msapplication-starturl", d.start_url || "/");
    e("msapplication-TileColor", d.theme_color);
    var b;
    (d.related_applications || []).filter(function(a) {
      return "itunes" == a.platform;
    }).forEach(function(a) {
      a.id ? b = a.id : (a = a.url.match(/id(\d+)/)) && (b = a[1]);
    });
    b && e("apple-itunes-app", "app-id\x3d" + b);
    d = d.icons || [];
    d.sort(function(a, b) {
      return parseInt(b.sizes, 10) - parseInt(a.sizes, 10);
    });
    d.forEach(function(a) {
      f.push({name:"link", a:{rel:"icon", href:a.src, sizes:a.sizes}});
      f.push({name:"link", a:{rel:"apple-touch-icon", href:a.src, sizes:a.sizes}});
    });
    return f;
  };
}, {}], 2:[function(i) {
  (function() {
    function h(c) {
      if (navigator.standalone) {
        var b;
        try {
          b = JSON.parse(window.localStorage["pwacompat.js"]);
        } catch (d) {
        }
        if (b) {
          c(b);
          return;
        }
      }
      var a = new XMLHttpRequest;
      a.onload = function() {
        var b = JSON.parse(a.responseText);
        try {
          window.localStorage["pwacompat.js"] = a.responseText;
        } catch (d) {
        }
        c(b);
      };
      a.open("GET", f.href);
      a.send();
    }
    function d(c) {
      i("./lib")(c).forEach(function(b) {
        var a = document.createElement(b.name), c;
        for (c in b.a) {
          a.setAttribute(c, b.a[c]);
        }
        document.head.appendChild(a);
      });
      navigator.standalone && e(c);
    }
    function e(c) {
      document.addEventListener("click", function(a) {
        "A" === a.target.tagName && ((new URL(a.target.href)).origin !== location.origin ? window.localStorage["pwacompat.js:out"] = location.href : (a.preventDefault(), window.location = a.target.href));
      });
      if (window.sessionStorage && !window.sessionStorage.loaded) {
        window.sessionStorage.loaded = !0;
        c = window.localStorage["pwacompat.js:out"] || c.start_url;
        delete window.localStorage["pwacompat.js:out"];
        var b = window.location.href + window.location.search;
        c && c != b && (c.replace(/#.*$/, "") == b ? window.location.hash = c.substr(c.indexOf("#")) : window.location = c);
      }
    }
    if (!navigator.serviceWorker && window.localStorage) {
      var f = document.head.querySelector('link[rel\x3d"manifest"]');
      f && f.href ? h(d) : console.warn('pwacompat.js can\'t operate: no \x3clink rel\x3d"manifest" ... /\x3e found');
    }
  })();
}, {"./lib":1}]}, {}, [2]);
<commit_msg>compile<commit_after>(function k(g,d,l){function e(a,b){if(!d[a]){if(!g[a]){var c="function"==typeof require&&require;if(!b&&c)return c(a,!0);if(f)return f(a,!0);c=Error("Cannot find module '"+a+"'");throw c.code="MODULE_NOT_FOUND",c;}c=d[a]={b:{}};g[a][0].call(c.b,function(b){var c=g[a][1][b];return e(c?c:b)},c,c.b,k,g,d,l)}return d[a].b}for(var f="function"==typeof require&&require,h=0;h<l.length;h++)e(l[h]);return e})({1:[function(m,g){g.b=function(d,g){function e(b,c){c&&f.push({name:"meta",a:{name:b,content:!0===
c?"yes":c}})}var f=[],h=d.icons||[];h.sort(function(b,c){return parseInt(c.sizes,10)-parseInt(b.sizes,10)});h.forEach(function(b){f.push({name:"link",a:{rel:"icon",href:b.src,sizes:b.sizes}});g||f.push({name:"link",a:{rel:"apple-touch-icon",href:b.src,sizes:b.sizes}})});if(g)return f;h=-1!==["standalone","fullscreen"].indexOf(d.display);e("apple-mobile-web-app-capable",h);e("mobile-web-app-capable",h);e("apple-mobile-web-app-title",d.short_name||d.name);e("msapplication-starturl",d.start_url||"/");
e("msapplication-TileColor",d.theme_color);var a;(d.related_applications||[]).filter(function(b){return"itunes"==b.platform}).forEach(function(b){b.id?a=b.id:(b=b.url.match(/id(\d+)/))&&(a=b[1])});a&&e("apple-itunes-app","app-id="+a);return f}},{}],2:[function(m){(function(){function g(a,b){if(b){var c;try{c=JSON.parse(window.localStorage["pwacompat.js"])}catch(n){}if(c){a(c);return}}var d=new XMLHttpRequest;d.onload=function(){var b=JSON.parse(d.responseText);try{window.localStorage["pwacompat.js"]=
d.responseText}catch(p){}a(b)};d.open("GET",f.href);d.send()}function d(a){m("./lib")(a,e).forEach(function(b){var c=document.createElement(b.name),a;for(a in b.a)c.setAttribute(a,b.a[a]);document.head.appendChild(c)});navigator.standalone&&l(a)}function l(a){document.addEventListener("click",function(a){"A"===a.target.tagName&&((new URL(a.target.href)).origin!==location.origin?window.localStorage["pwacompat.js:out"]=location.href:(a.preventDefault(),window.location=a.target.href))});if(window.sessionStorage&&
!window.sessionStorage.loaded){window.sessionStorage.loaded=!0;a=window.localStorage["pwacompat.js:out"]||a.start_url;delete window.localStorage["pwacompat.js:out"];var b=window.location.href+window.location.search;a&&a!=b&&(a.replace(/#.*$/,"")==b?window.location.hash=a.substr(a.indexOf("#")):window.location=a)}}var e=!!navigator.serviceWorker;if(!document.head.querySelector('link[type|="icon"]')||!e){var f=document.head.querySelector('link[rel="manifest"]');if(f&&f.href){var h=window.performance&&
1!==window.performance.navigation.type;g(d,navigator.standalone||h)}else console.warn('pwacompat.js can\'t operate: no <link rel="manifest" ... /> found')}})()},{"./lib":1}]},{},[2]);
<|endoftext|>


<commit_before>/*
 * c_cpp_highlight_rules.js
 *
 * Copyright (C) 2009-12 by RStudio, Inc.
 *
 * The Original Code is Ajax.org Code Editor (ACE).
 *
 * The Initial Developer of the Original Code is
 * Ajax.org B.V.
 * Portions created by the Initial Developer are Copyright (C) 2010
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *      Fabian Jakobs <fabian AT ajax DOT org>
 *      Gastón Kleiman <gaston.kleiman AT gmail DOT com>
 *
 * Based on Bespin's C/C++ Syntax Plugin by Marc McIntyre.
 *
 * Unless you have received this program directly from RStudio pursuant
 * to the terms of a commercial license agreement with RStudio, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */

define("mode/c_cpp_highlight_rules", function(require, exports, module) {

var oop = require("ace/lib/oop");
var lang = require("ace/lib/lang");
var DocCommentHighlightRules = require("mode/doc_comment_highlight_rules").DocCommentHighlightRules;
var TextHighlightRules = require("ace/mode/text_highlight_rules").TextHighlightRules;
var TexHighlightRules = require("mode/tex_highlight_rules").TexHighlightRules;
var RHighlightRules = require("mode/r_highlight_rules").RHighlightRules;

var c_cppHighlightRules = function() {

   function escapeRegExp(str) {
      return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
   }

   var keywords = lang.arrayToMap([
      "alignas", "alignof", "and", "and_eq", "asm", "auto", "bitand",
       "bitor", "bool", "break", "case", "catch", "char", "char16_t",
       "char32_t", "class", "compl", "const", "constexpr",
       "const_cast", "continue", "decltype", "default", "delete",
       "do", "double", "dynamic_cast", "else", "enum", "explicit",
       "export", "extern", "false", "float", "for", "friend", "goto",
       "if", "inline", "int", "in", "long", "mutable", "namespace",
       "new", "noexcept", "not", "not_eq", "nullptr", "or", "or_eq",
       "private", "protected", "public", "register",
       "reinterpret_cast", "return", "short", "signed", "sizeof",
       "static", "static_assert", "static_cast", "struct", "switch",
       "template", "this", "thread_local", "throw", "true", "try",
       "typedef", "typeid", "typeof", "typename", "union", "unsigned",
       "using", "virtual", "void", "volatile", "wchar_t", "while",
       "xor", "xor_eq"
   ]);

   var preProcTokens = [
      "include", "pragma", "line", "define", "undef", "ifdef", "ifndef",
      "if", "else", "elif", "endif", "warning", "error"
   ];

   var buildinConstants = lang.arrayToMap(
      ("NULL").split("|")
   );

   var operatorTokens = [

      ">>=", "<<=", "new", "delete",

      "<<", ">>", "&&", "||", "==", "!=", "<=", ">=", "::",
      "*=", "+=", "-=", "/=", "++", "--", "<>", "&=", "^=",
      "%=", "->",

      "!", "$", "&", "|", "*", "-", "+", "~", "="

   ];

   var reOperatorTokens = operatorTokens.map(function(x) {
      return escapeRegExp(x);
   }).join("|");

   var reOperator =
      [",", "()", "[]", "->*", "->"]
      .concat(operatorTokens)
      .map(function(x) {
         return escapeRegExp(x);
      });

   reOperator = ["new\\s*\\[\\]", "delete\\s*\\[\\]"].concat(reOperator);
   reOperator = "operator\\s*(?:" + reOperator.join("|") + ")|operator\\s+[\\w_]+";

   // regexp must not have capturing parentheses. Use (?:) instead.
   // regexps are ordered -> the first match is used

   this.$rules = {
      "start" : [
         {
            // Attributes
            token: "comment.doc.tag",
            regex: "\\/\\/\\s*\\[\\[.*\\]\\].*$"
         }, {
            // Roxygen
            token : "comment",
            regex : "\\/\\/'",
            next : "rd-start"
         }, {
            // Standard comment
            token : "comment",
            regex : "\\/\\/.*$"
         },
         DocCommentHighlightRules.getStartRule("doc-start"),
         {
            token : "comment", // multi line comment
            merge : true,
            regex : "\\/\\*",
            next : "comment"
         }, {
            token : "string", // single line
            regex : '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]'
         }, {
            token : "string", // multi line string start
            merge : true,
            regex : '["].*\\\\$',
            next : "qqstring"
         }, {
            token : "string", // single line
            regex : "['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']"
         }, {
            token : "string", // multi line string start
            merge : true,
            regex : "['].*\\\\$",
            next : "qstring"
         }, {
            token : "constant.numeric", // hex
            regex : "0[xX][0-9a-fA-F]+\\b"
         }, {
            token : "constant.numeric", // float
            regex : "[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b"
         }, {
            token : "keyword.preproc",
            regex : "#\\s*include\\b",
            next : "include"
         }, {
            token : "keyword.preproc", // pre-compiler directives
            regex : "(?:" + preProcTokens.map(function(x) { return "#\\s*" + x + "\\b"; }).join("|") + ")"
         }, {
            token : "variable.language", // compiler-specific constructs
            regex : "\\b__\\S+__\\b"
         }, {
            token: "keyword",
            regex: reOperator
         }, {
            token : function(value) {
               if (value == "this")
                  return "variable.language";
               else if (keywords.hasOwnProperty(value))
                  return "keyword";
               else if (buildinConstants.hasOwnProperty(value))
                  return "constant.language";
               else
                  return "identifier";
            },
            regex : "[a-zA-Z_$][a-zA-Z0-9_$]*\\b"
         }, {
            token : "keyword.operator",
            regex : reOperatorTokens
         }, {
            token : "punctuation.operator",
            regex : "\\?|\\:|\\,|\\;|\\.|\\\\"
         }, {
            // Obviously these are neither keywords nor operators, but
            // labelling them as such was the easiest way to get them
            // to be colored distinctly from regular text
            token : "paren.keyword.operator",
            regex : "[[({<]"
         }, {
            // Obviously these are neither keywords nor operators, but
            // labelling them as such was the easiest way to get them
            // to be colored distinctly from regular text
            token : "paren.keyword.operator",
            regex : "[\\])}>]"
         }, {
            token : "text",
            regex : "\\s+"
         }
      ],
      "comment" : [
         {
            token : "comment", // closing comment
            regex : ".*?\\*\\/",
            next : "start"
         }, {
            token : "comment", // comment spanning whole line
            merge : true,
            regex : ".+"
         }
      ],
      "qqstring" : [
         {
            token : "string",
            regex : '(?:(?:\\\\.)|(?:[^"\\\\]))*?"',
            next : "start"
         }, {
            token : "string",
            merge : true,
            regex : '.+'
         }
      ],
      "qstring" : [
         {
            token : "string",
            regex : "(?:(?:\\\\.)|(?:[^'\\\\]))*?'",
            next : "start"
         }, {
            token : "string",
            merge : true,
            regex : '.+'
         }
      ],
      "include" : [
         {
            token : "string", // <CONSTANT>
            regex : /<.+>/,
            next : "start"
         },
         {
            token : "string",
            regex : /\".+\"/,
            next : "start"
         }
      ]

   };

   var rdRules = new TexHighlightRules("comment").getRules();

   // Make all embedded TeX virtual-comment so they don't interfere with
   // auto-indent.
   for (var i = 0; i < rdRules["start"].length; i++) {
      rdRules["start"][i].token += ".virtual-comment";
   }

   this.addRules(rdRules, "rd-");
   this.$rules["rd-start"].unshift({
      token: "text",
      regex: "^",
      next: "start"
   });
   this.$rules["rd-start"].unshift({
      token : "keyword",
      regex : "@(?!@)[^ ]*"
   });
   this.$rules["rd-start"].unshift({
      token : "comment",
      regex : "@@"
   });
   this.$rules["rd-start"].push({
      token : "comment",
      regex : "[^%\\\\[({\\])}]+"
   });

   this.embedRules(DocCommentHighlightRules, "doc-",
                   [ DocCommentHighlightRules.getEndRule("start") ]);

   // Embed R syntax highlighting
   this.$rules["start"].unshift({
      token: "support.function.codebegin",
      regex: "^\\s*\\/\\*{3,}\\s+[Rr]\\s*$",
      next: "r-start"
   });

   var rRules = new RHighlightRules().getRules();
   this.addRules(rRules, "r-");
   this.$rules["r-start"].unshift({
      token: "support.function.codeend",
      regex: "\\*\\/",
      next: "start"
   });

};

oop.inherits(c_cppHighlightRules, TextHighlightRules);

exports.c_cppHighlightRules = c_cppHighlightRules;
});
<commit_msg>highlighting for integer, binary, user literals in c++ mode<commit_after>/*
 * c_cpp_highlight_rules.js
 *
 * Copyright (C) 2009-12 by RStudio, Inc.
 *
 * The Original Code is Ajax.org Code Editor (ACE).
 *
 * The Initial Developer of the Original Code is
 * Ajax.org B.V.
 * Portions created by the Initial Developer are Copyright (C) 2010
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *      Fabian Jakobs <fabian AT ajax DOT org>
 *      Gastón Kleiman <gaston.kleiman AT gmail DOT com>
 *
 * Based on Bespin's C/C++ Syntax Plugin by Marc McIntyre.
 *
 * Unless you have received this program directly from RStudio pursuant
 * to the terms of a commercial license agreement with RStudio, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */

define("mode/c_cpp_highlight_rules", function(require, exports, module) {

var oop = require("ace/lib/oop");
var lang = require("ace/lib/lang");
var DocCommentHighlightRules = require("mode/doc_comment_highlight_rules").DocCommentHighlightRules;
var TextHighlightRules = require("ace/mode/text_highlight_rules").TextHighlightRules;
var TexHighlightRules = require("mode/tex_highlight_rules").TexHighlightRules;
var RHighlightRules = require("mode/r_highlight_rules").RHighlightRules;

var c_cppHighlightRules = function() {

   function escapeRegExp(str) {
      return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
   }

   var keywords = lang.arrayToMap([
      "alignas", "alignof", "and", "and_eq", "asm", "auto", "bitand",
       "bitor", "bool", "break", "case", "catch", "char", "char16_t",
       "char32_t", "class", "compl", "const", "constexpr",
       "const_cast", "continue", "decltype", "default", "delete",
       "do", "double", "dynamic_cast", "else", "enum", "explicit",
       "export", "extern", "false", "float", "for", "friend", "goto",
       "if", "inline", "int", "in", "long", "mutable", "namespace",
       "new", "noexcept", "not", "not_eq", "nullptr", "or", "or_eq",
       "private", "protected", "public", "register",
       "reinterpret_cast", "return", "short", "signed", "sizeof",
       "static", "static_assert", "static_cast", "struct", "switch",
       "template", "this", "thread_local", "throw", "true", "try",
       "typedef", "typeid", "typeof", "typename", "union", "unsigned",
       "using", "virtual", "void", "volatile", "wchar_t", "while",
       "xor", "xor_eq"
   ]);

   var preProcTokens = [
      "include", "pragma", "line", "define", "undef", "ifdef", "ifndef",
      "if", "else", "elif", "endif", "warning", "error"
   ];

   var buildinConstants = lang.arrayToMap(
      ("NULL").split("|")
   );

   var operatorTokens = [

      ">>=", "<<=", "new", "delete",

      "<<", ">>", "&&", "||", "==", "!=", "<=", ">=", "::",
      "*=", "+=", "-=", "/=", "++", "--", "<>", "&=", "^=",
      "%=", "->",

      "!", "$", "&", "|", "*", "-", "+", "~", "="

   ];

   var reOperatorTokens = operatorTokens.map(function(x) {
      return escapeRegExp(x);
   }).join("|");

   var reOperator =
      [",", "()", "[]", "->*", "->"]
      .concat(operatorTokens)
      .map(function(x) {
         return escapeRegExp(x);
      });

   reOperator = ["new\\s*\\[\\]", "delete\\s*\\[\\]"].concat(reOperator);
   reOperator = "operator\\s*(?:" + reOperator.join("|") + ")|operator\\s+[\\w_]+";

   // regexp must not have capturing parentheses. Use (?:) instead.
   // regexps are ordered -> the first match is used

   this.$rules = {
      "start" : [
         {
            // Attributes
            token: "comment.doc.tag",
            regex: "\\/\\/\\s*\\[\\[.*\\]\\].*$"
         }, {
            // Roxygen
            token : "comment",
            regex : "\\/\\/'",
            next : "rd-start"
         }, {
            // Standard comment
            token : "comment",
            regex : "\\/\\/.*$"
         },
         DocCommentHighlightRules.getStartRule("doc-start"),
         {
            token : "comment", // multi line comment
            merge : true,
            regex : "\\/\\*",
            next : "comment"
         }, {
            token : "string", // single line
            regex : '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]'
         }, {
            token : "string", // multi line string start
            merge : true,
            regex : '["].*\\\\$',
            next : "qqstring"
         }, {
            token : "string", // single line
            regex : "['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']"
         }, {
            token : "string", // multi line string start
            merge : true,
            regex : "['].*\\\\$",
            next : "qstring"
         }, {
            token : "constant.numeric", // hex
            regex : "0[xX][0-9a-fA-F]+\\b"
         }, {
            token : "constant.numeric", // binary literal
            regex : "0[bB][01']+\\b"
         }, {
            token : "constant.numeric", // float
            regex : "[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?(?:(?:[fF])|(?:(?:[uU]?(?:(?:l?l?)|(?:L?L?))?)|(?:(?:(?:l?l?)|(?:L?L?))[uU]?))|(?:_\\w+))?\\b"
         }, {
            token : "keyword.preproc",
            regex : "#\\s*include\\b",
            next : "include"
         }, {
            token : "keyword.preproc", // pre-compiler directives
            regex : "(?:" + preProcTokens.map(function(x) { return "#\\s*" + x + "\\b"; }).join("|") + ")"
         }, {
            token : "variable.language", // compiler-specific constructs
            regex : "\\b__\\S+__\\b"
         }, {
            token: "keyword",
            regex: reOperator
         }, {
            token : function(value) {
               if (value == "this")
                  return "variable.language";
               else if (keywords.hasOwnProperty(value))
                  return "keyword";
               else if (buildinConstants.hasOwnProperty(value))
                  return "constant.language";
               else
                  return "identifier";
            },
            regex : "[a-zA-Z_$][a-zA-Z0-9_$]*\\b"
         }, {
            token : "keyword.operator",
            regex : reOperatorTokens
         }, {
            token : "punctuation.operator",
            regex : "\\?|\\:|\\,|\\;|\\.|\\\\"
         }, {
            // Obviously these are neither keywords nor operators, but
            // labelling them as such was the easiest way to get them
            // to be colored distinctly from regular text
            token : "paren.keyword.operator",
            regex : "[[({<]"
         }, {
            // Obviously these are neither keywords nor operators, but
            // labelling them as such was the easiest way to get them
            // to be colored distinctly from regular text
            token : "paren.keyword.operator",
            regex : "[\\])}>]"
         }, {
            token : "text",
            regex : "\\s+"
         }
      ],
      "comment" : [
         {
            token : "comment", // closing comment
            regex : ".*?\\*\\/",
            next : "start"
         }, {
            token : "comment", // comment spanning whole line
            merge : true,
            regex : ".+"
         }
      ],
      "qqstring" : [
         {
            token : "string",
            regex : '(?:(?:\\\\.)|(?:[^"\\\\]))*?"',
            next : "start"
         }, {
            token : "string",
            merge : true,
            regex : '.+'
         }
      ],
      "qstring" : [
         {
            token : "string",
            regex : "(?:(?:\\\\.)|(?:[^'\\\\]))*?'",
            next : "start"
         }, {
            token : "string",
            merge : true,
            regex : '.+'
         }
      ],
      "include" : [
         {
            token : "string", // <CONSTANT>
            regex : /<.+>/,
            next : "start"
         },
         {
            token : "string",
            regex : /\".+\"/,
            next : "start"
         }
      ]

   };

   var rdRules = new TexHighlightRules("comment").getRules();

   // Make all embedded TeX virtual-comment so they don't interfere with
   // auto-indent.
   for (var i = 0; i < rdRules["start"].length; i++) {
      rdRules["start"][i].token += ".virtual-comment";
   }

   this.addRules(rdRules, "rd-");
   this.$rules["rd-start"].unshift({
      token: "text",
      regex: "^",
      next: "start"
   });
   this.$rules["rd-start"].unshift({
      token : "keyword",
      regex : "@(?!@)[^ ]*"
   });
   this.$rules["rd-start"].unshift({
      token : "comment",
      regex : "@@"
   });
   this.$rules["rd-start"].push({
      token : "comment",
      regex : "[^%\\\\[({\\])}]+"
   });

   this.embedRules(DocCommentHighlightRules, "doc-",
                   [ DocCommentHighlightRules.getEndRule("start") ]);

   // Embed R syntax highlighting
   this.$rules["start"].unshift({
      token: "support.function.codebegin",
      regex: "^\\s*\\/\\*{3,}\\s+[Rr]\\s*$",
      next: "r-start"
   });

   var rRules = new RHighlightRules().getRules();
   this.addRules(rRules, "r-");
   this.$rules["r-start"].unshift({
      token: "support.function.codeend",
      regex: "\\*\\/",
      next: "start"
   });

};

oop.inherits(c_cppHighlightRules, TextHighlightRules);

exports.c_cppHighlightRules = c_cppHighlightRules;
});
<|endoftext|>


<commit_before>var cadence = require('cadence')
var Cliffhanger = require('cliffhanger')
var Dispatcher = require('inlet/dispatcher')
var url = require('url')
var assert = require('assert')

function Conduit (id) {
    this._cliffhanger = new Cliffhanger()
    this._listeners = {}
    this._instanceId = id
    var dispatcher = new Dispatcher(this)
    dispatcher.dispatch('GET /', 'index')
    dispatcher.dispatch('POST /bootstrap', 'bootstrap')
    dispatcher.dispatch('POST /join', 'join')
    dispatcher.dispatch('POST /kibitz', 'kibitz')
    dispatcher.dispatch('POST /health', 'health')
    dispatcher.dispatch('GET /colleagues', 'colleagues')
    this.dispatcher = dispatcher
// TODO Should be able to time out explicitly on socket close.
    setInterval(function () {
        this._cliffhanger.expire(Date.now() - 5000)
    }.bind(this), 1000).unref()
}

Conduit.prototype.index = cadence(function () {
    return 'Compassion Conduit API\n'
})

Conduit.prototype.connection = function (ws) {
    var query = url.parse(ws.upgradeReq.url, true).query
    if (query.islandName && query.colleagueId) {
        var listeners = this._listeners, cliffhanger = this._cliffhanger

        var key = '(' + query.islandName + ')' + query.colleagueId

        var listener = listeners[key]
        if (listener != null) {
            listener.close.call(null)
        }
        listener = listeners[key] = { query: query, close: close, ws: ws }

        ws.on('message', message)
        ws.on('close', close)

        function message (event) {
            var message = JSON.parse(event)
            cliffhanger.resolve(message.cookie, [ null, message.body ])
        }

        function close () {
            delete listeners[key]
            ws.removeListener('close', close)
            ws.removeListener('message', message)
            ws.close()
            ws.terminate()
        }
    }
}

Conduit.prototype._send = cadence(function (async, type, request) {
    var body = request.body
    assert(body.islandName && body.colleagueId)
    var key = '(' + body.islandName + ')' + body.colleagueId
    var listener = this._listeners[key]
    if (listener != null) {
        var cookie = this._cliffhanger.invoke(async())
        listener.ws.send(JSON.stringify({ type: type, cookie: cookie, body: body }))
        return
    }
    request.raise(404)
})

; [ 'bootstrap', 'join', 'kibitz', 'health' ].forEach(function (name) {
    Conduit.prototype[name] = cadence(function (async, request) {
        this._send(name, request, async())
    })
})

Conduit.prototype.colleagues = cadence(function (async) {
    var inquire = []
    for (var key in this._listeners) {
        inquire.push(this._listeners[key].query)
    }
    return {
        instanceId: this._instanceId,
        colleagues: inquire
    }
})

module.exports = Conduit
<commit_msg>Add out of band channel.<commit_after>var cadence = require('cadence')
var Cliffhanger = require('cliffhanger')
var Dispatcher = require('inlet/dispatcher')
var url = require('url')
var assert = require('assert')

function Conduit (id) {
    this._cliffhanger = new Cliffhanger()
    this._listeners = {}
    this._instanceId = id
    var dispatcher = new Dispatcher(this)
    dispatcher.dispatch('GET /', 'index')
    dispatcher.dispatch('POST /bootstrap', 'bootstrap')
    dispatcher.dispatch('POST /join', 'join')
    dispatcher.dispatch('POST /kibitz', 'kibitz')
    dispatcher.dispatch('POST /oob', 'oob')
    dispatcher.dispatch('POST /health', 'health')
    dispatcher.dispatch('GET /colleagues', 'colleagues')
    this.dispatcher = dispatcher
// TODO Should be able to time out explicitly on socket close.
    setInterval(function () {
        this._cliffhanger.expire(Date.now() - 5000)
    }.bind(this), 1000).unref()
}

Conduit.prototype.index = cadence(function () {
    return 'Compassion Conduit API\n'
})

Conduit.prototype.connection = function (ws) {
    var query = url.parse(ws.upgradeReq.url, true).query
    if (query.islandName && query.colleagueId) {
        var listeners = this._listeners, cliffhanger = this._cliffhanger

        var key = '(' + query.islandName + ')' + query.colleagueId

        var listener = listeners[key]
        if (listener != null) {
            listener.close.call(null)
        }
        listener = listeners[key] = { query: query, close: close, ws: ws }

        ws.on('message', message)
        ws.on('close', close)

        function message (event) {
            var message = JSON.parse(event)
            cliffhanger.resolve(message.cookie, [ null, message.body ])
        }

        function close () {
            delete listeners[key]
            ws.removeListener('close', close)
            ws.removeListener('message', message)
            ws.close()
            ws.terminate()
        }
    }
}

Conduit.prototype._send = cadence(function (async, type, request) {
    var body = request.body
    console.log(body)
    assert(body.islandName && body.colleagueId)
    var key = '(' + body.islandName + ')' + body.colleagueId
    var listener = this._listeners[key]
    if (listener != null) {
        var cookie = this._cliffhanger.invoke(async())
        listener.ws.send(JSON.stringify({ type: type, cookie: cookie, body: body }))
        return
    }
    request.raise(404)
})

; [ 'oob', 'bootstrap', 'join', 'kibitz', 'health' ].forEach(function (name) {
    Conduit.prototype[name] = cadence(function (async, request) {
        this._send(name, request, async())
    })
})

Conduit.prototype.colleagues = cadence(function (async) {
    var inquire = []
    for (var key in this._listeners) {
        inquire.push(this._listeners[key].query)
    }
    return {
        instanceId: this._instanceId,
        colleagues: inquire
    }
})

module.exports = Conduit
<|endoftext|>


<commit_before>import Debug from 'debug'

import { constructStyles } from '../../fashionistas/timed'

import {
  flatten,
  isUsingSpring,
  isUsingTime,
  makeError,
} from '../../utils'

import {
  DEFAULT_ANIMATION_NAME,
  DEFAULT_EASING_FN,
  IS_PRODUCTION,
} from '../../constants'

import {
  throwIfAnimationNotValid,
  throwIfPhaseNotValid,
} from './validator'

const debug = Debug('react-animatronics:machines:animatronics');

export const calculateEasingProgress = (
  easingFn: Function,
  duration: number,
  elapsedTime: number,
): number => (
  easingFn(elapsedTime / (duration === 0 ? elapsedTime : duration))
);

export const makeSequence = state => animationName => {
  const { createAnimationSequences, nodes } = state;
  const areSequencesStatic = typeof createAnimationSequences === 'function';

  const sequences = areSequencesStatic
    ? createAnimationSequences(nodes)
    : createAnimationSequences;

  const namedSequences = Array.isArray(sequences)
    ? { [DEFAULT_ANIMATION_NAME]: sequences }
    : sequences;

  if (!IS_PRODUCTION) {
    if (namedSequences[animationName] == null) {
      throw makeError(
        `Attempted to run an animation named "${ animationName }", but there is no such`
        + ` named animation. The animations you have defined are:`
        + ` [${ Object.keys(namedSequences).filter(name => name !== DEFAULT_ANIMATION_NAME) }]`
      );
    }
  }

  const sequence = areSequencesStatic
    ? namedSequences[animationName]
    : namedSequences[animationName](nodes);

  if (!IS_PRODUCTION) {
    sequence.forEach(phase => throwIfPhaseNotValid(phase, nodes));
  }

  return sequence;
};

const getNumPhases = state => animationName => {
  const sequence = makeSequence(state)(animationName);
  return sequence.length;
};

const runTimedAnimation = mutators => (animationName, componentName, animation, index) => {
  const {
    from: fromStyles,
    to: toStyles,
    duration,
    easingFn = DEFAULT_EASING_FN,
  } = animation;

  mutators.createTimedJobMachine({
    index,
    componentName,
    duration,
  });

  const job = elapsedTime => {
    const progress = calculateEasingProgress(easingFn, duration, elapsedTime);
    const updatedStyles = constructStyles(fromStyles, toStyles, progress);
    mutators.updateComponentStyles({ componentName, updatedStyles });
  }

  const onCompleteJob = () => {
    mutators.updateComponentStyles({ componentName, toStyles });
    mutators.countdownAnimations({ animationName });
  }

  mutators.registerTimedJob({ index, componentName, job });
  mutators.registerTimedOnCompletedJob({ index, componentName, onCompleteJob });
  mutators.startTimedJob({ index, componentName });
};

const runSpringAnimation = mutators => (animationName, componentName, animation, index) => {
  const {
    from: fromStyles,
    to: toStyles,
    stiffness,
    damping,
  } = animation;

  mutators.createSpringMachine({
    index,
    componentName,
    fromStyles,
    toStyles,
    stiffness,
    damping,
  });
  mutators.createEndlessJobMachine({ index, componentName });

  const onNext = updatedStyles => {
    mutators.updateComponentStyles({ componentName, updatedStyles });
  }

  const onComplete = updatedStyles => {
    mutators.updateComponentStyles({ componentName, updatedStyles });
    mutators.stopEndlessJobMachine({ index, componentName });
    mutators.countdownAnimations({ animationName });
  }

  const job = () => {
    mutators.runNextSpringFrame({ index, componentName, onNext, onComplete });
  }

  mutators.registerEndlessJob({ index, componentName, job });
  mutators.startEndlessJobMachine({ index, componentName });
};

export const play = (state, mutators) => (animationName, onComplete) => {
  // IMPROVE: It's weird that getNumPhases calls makeSequence internally, but
  // things are the way they are so the user does not need to write each
  // phase as a function - having this inefficiency makes the API nicer.
  const numPhases = getNumPhases(state)(animationName);

  if (!IS_PRODUCTION) {
    if (numPhases === 0) {
      throw makeError(
        `Attemped to run an empty animation sequence. Check <Animatronics/>`
        + ` or withAnimatronics and make sure youre're returning either an Array or`
        + ` an Object from the "createAnimationSequences" function. Here's what your`
        + ` function looks like right now:`
        + `\n`
        + `${ state.createAnimationSequences.toString() }`
      );
    }
  }

  mutators.createPhasesCountdownMachine({
    animationName,
    job: onComplete,
    numPhases,
  });

  const runPhase = (phaseIndex) => {
    const sequence = makeSequence(state)(animationName);
    const phase = sequence[phaseIndex];
    debug('executing phase %O', phase);
    const componentNames = Object.keys(phase);
    const numAnimations = componentNames.reduce(
      (result, componentNames) => {
        const rawAnimation = phase[componentNames];
        const animations = Array.isArray(rawAnimation) ? rawAnimation : [rawAnimation];
        return result + animations.length;
      },
      0
    );

    const job = () => {
      mutators.countdownPhases({ animationName });
      if (phaseIndex < (numPhases - 1)) {
        runPhase(phaseIndex + 1);
      }
    }

    mutators.createAnimationCountdownMachine({
      animationName,
      job,
      numAnimations,
    });

    componentNames.forEach(componentName => {
      const rawAnimation = phase[componentName];
      const animations = Array.isArray(rawAnimation) ? rawAnimation : [rawAnimation];

      const runAnimation = (animation, index) => {
        if (isUsingTime(animation)) {
          runTimedAnimation(mutators)(animationName, componentName, animation, index);
        } else if (isUsingSpring(animation)) {
          runSpringAnimation(mutators)(animationName, componentName, animation, index);
        }
      };

      animations.forEach((animation, index) => {
        const { delay } = animation;
        if (delay == null) {
          runAnimation(animation, index);
        } else {
          mutators.runDelayedAnimation({ componentName, delay, runAnimation, animation, index });
        }
      });
    });
  }

  runPhase(0);
};

const stop = (state, mutators) => () => {
  mutators.stopMachine();
};

const reset = (state, mutators) => () => {
  reducer.stopMachine();
  reducer.resetMachine();
};

const registerComponent = (state, mutators) => (componentName, node, styleUpdater, styleResetter) => {
  mutators.registerComponent({
    componentName,
    node,
    styleUpdater,
    styleResetter,
  });
}

const unregisterComponent = (state, mutators) => componentName => {
  mutators.unregisterComponent({ componentName });
}

const setCreateAnimationSequences = (state, mutators) => createAnimationSequences => {
  mutators.setCreateAnimationSequences({
    createAnimationSequences,
  });
}

// EXPERIMENT: Isolating any shared state mutations here.
export const makeMutators = (machinist, state) => ({

  createSpringMachine: action => {
    const {
      index,
      componentName,
      fromStyles,
      toStyles,
      stiffness,
      damping,
    } = action;
    const machine = machinist.makeSpringMachine(
      fromStyles,
      toStyles,
      stiffness,
      damping
    );
    if (!state.springMachines[componentName]) {
      state.springMachines[componentName] = [];
    }
    state.springMachines[componentName][index] = machine;
  },

  createEndlessJobMachine: action => {
    const { index, componentName } = action;
    const machine = machinist.makeEndlessJobMachine(
      machinist.requestAnimationFrame,
      machinist.cancelAnimationFrame
    );
    if (!state.endlessJobMachines[componentName]) {
      state.endlessJobMachines[componentName] = [];
    }
    state.endlessJobMachines[componentName][index] = machine;
  },

  createAnimationCountdownMachine: action => {
    const { numAnimations, job, animationName } = action;
    const machine = machinist.makeCountdownJobMachine(numAnimations);
    machine.registerJob(job);
    state.animationCountdownMachines[animationName] = machine;
  },

  createPhasesCountdownMachine: action => {
    const { numPhases, job, animationName } = action;
    const machine = machinist.makeCountdownJobMachine(numPhases);
    machine.registerJob(job);
    state.phasesCountdownMachines[animationName] = machine;
  },

  createTimedJobMachine: action => {
    const { index, componentName, duration } = action;
    const machine = machinist.makeTimedJobMachine(
      duration,
      machinist.requestAnimationFrame,
      machinist.cancelAnimationFrame,
      machinist.now,
    );
    if (!state.timedJobMachines[componentName]) {
      state.timedJobMachines[componentName] = [];
    }
    state.timedJobMachines[componentName][index] = machine;
  },

  countdownAnimations: action => {
    const { animationName } = action;
    state.animationCountdownMachines[animationName].countdown();
  },

  countdownPhases: action => {
    const { animationName } = action;
    state.phasesCountdownMachines[animationName].countdown();
  },

  registerComponent: action => {
    const { componentName, node, styleUpdater, styleResetter } = action;
    debug('registering component %s %o', componentName, node);
    state.nodes[componentName] = node;
    state.styleUpdaters[componentName] = styleUpdater;
    state.styleResetters[componentName] = styleResetter;
  },

  registerEndlessJob: action => {
    const { index, componentName, job } = action;
    state.endlessJobMachines[componentName][index].registerJob(job);
  },

  registerTimedJob: action => {
    const { index, componentName, job } = action;
    state.timedJobMachines[componentName][index].registerJob(job);
  },

  registerTimedOnCompletedJob: action => {
    const { index, componentName, onCompleteJob } = action;
    state.timedJobMachines[componentName][index].registerOnCompleteJob(onCompleteJob);
  },

  resetMachine: action => {
    Object.keys(state.styleResetters).map(componentName => {
      state.styleResetters[componentName]();
    });
  },

  runNextSpringFrame: action => {
    const {
      index,
      componentName,
      onNext,
      onComplete,
    } = action;
    state.springMachines[componentName][index].runNextFrame(onNext, onComplete);
  },

  runDelayedAnimation: action => {
    const { componentName, delay, runAnimation, animation, index } = action;
    state.timeouts[componentName] = machinist.setTimeout(
      () => runAnimation(animation, index),
      delay
    );
  },

  setCreateAnimationSequences: action => {
    const { createAnimationSequences } = action;
    debug('setting updated createAnimationSequences %s', createAnimationSequences);
    state.createAnimationSequences = createAnimationSequences;
  },

  startTimedJob: action => {
    const { index, componentName } = action;
    state.timedJobMachines[componentName][index].start();
  },

  startEndlessJobMachine: action => {
    debug('starting endless job machine i.e. spring %O', action);
    const { index, componentName } = action;
    state.endlessJobMachines[componentName][index].start();
  },

  stopMachine: action => {
    flatten(Object.values(state.timedJobMachines))
      .forEach(machine => machine.stop());
    Object.values(state.timeouts)
      .forEach(timeout => machinist.clearTimeout(timeout));
    state.timedJobMachines = {};
    state.timeouts = {};
  },

  stopEndlessJobMachine: action => {
    const { index, componentName } = action;
    state.endlessJobMachines[componentName][index].stop();
  },

  unregisterComponent: action => {
    const { componentName } = action;
    delete state.nodes[componentName];
    delete state.styleUpdaters[componentName];
  },

  updateComponentStyles: action => {
    const { componentName, updatedStyles } = action;
    const updateStyles = state.styleUpdaters[componentName];
    if (updateStyles) {
      updateStyles(updatedStyles);
    }
  }
});

export const makeAnimatronicsMachine = machinist => createAnimationSequences => {
  const state = {
    nodes: {},
    styleUpdaters: {},
    styleResetters: {},
    timedJobMachines: {},
    springMachines: {},
    endlessJobMachines: {},
    timeouts: {},
    animationCountdownMachines: {},
    phasesCountdownMachines: {},
    createAnimationSequences,
  };

  const mutators = makeMutators(machinist, state);

  const animatronicsMachine = {
    play: play(state, mutators),
    stop: stop(state, mutators),
    reset: reset(state, mutators),
    registerComponent: registerComponent(state, mutators),
    unregisterComponent: unregisterComponent(state, mutators),
    setCreateAnimationSequences: setCreateAnimationSequences(state, mutators),
  };

  return animatronicsMachine;
}
<commit_msg>:hammer: [animatronics] Record every animation frame when not running production<commit_after>import Debug from 'debug'

import { constructStyles } from '../../fashionistas/timed'
import Recorder from '../../recorder'

import {
  flatten,
  isUsingSpring,
  isUsingTime,
  makeError,
} from '../../utils'

import {
  DEFAULT_ANIMATION_NAME,
  DEFAULT_EASING_FN,
  IS_PRODUCTION,
} from '../../constants'

import {
  throwIfAnimationNotValid,
  throwIfPhaseNotValid,
} from './validator'

const debug = Debug('react-animatronics:machines:animatronics');

export const calculateEasingProgress = (
  easingFn: Function,
  duration: number,
  elapsedTime: number,
): number => (
  easingFn(elapsedTime / (duration === 0 ? elapsedTime : duration))
);

export const makeSequence = state => animationName => {
  const { createAnimationSequences, nodes } = state;
  const areSequencesStatic = typeof createAnimationSequences === 'function';

  const sequences = areSequencesStatic
    ? createAnimationSequences(nodes)
    : createAnimationSequences;

  const namedSequences = Array.isArray(sequences)
    ? { [DEFAULT_ANIMATION_NAME]: sequences }
    : sequences;

  if (!IS_PRODUCTION) {
    if (namedSequences[animationName] == null) {
      throw makeError(
        `Attempted to run an animation named "${ animationName }", but there is no such`
        + ` named animation. The animations you have defined are:`
        + ` [${ Object.keys(namedSequences).filter(name => name !== DEFAULT_ANIMATION_NAME) }]`
      );
    }
  }

  const sequence = areSequencesStatic
    ? namedSequences[animationName]
    : namedSequences[animationName](nodes);

  if (!IS_PRODUCTION) {
    sequence.forEach(phase => throwIfPhaseNotValid(phase, nodes));
  }

  return sequence;
};

const getNumPhases = state => animationName => {
  const sequence = makeSequence(state)(animationName);
  return sequence.length;
};

const runTimedAnimation = (state, mutators) => (animationName, componentName, animation, index) => {
  const {
    from: fromStyles,
    to: toStyles,
    duration,
    easingFn = DEFAULT_EASING_FN,
  } = animation;

  mutators.createTimedJobMachine({
    index,
    componentName,
    duration,
  });

  if (!IS_PRODUCTION) {
    Recorder.reset(animationName);
  }

  const job = elapsedTime => {
    const progress = calculateEasingProgress(easingFn, duration, elapsedTime);
    const updatedStyles = constructStyles(fromStyles, toStyles, progress);
    mutators.updateComponentStyles({ componentName, updatedStyles });

    if (!IS_PRODUCTION) {
      const updateStyles = state.styleUpdaters[componentName];
      Recorder.record({ animationName, componentName, elapsedTime, updatedStyles, updateStyles });
    }
  }

  const onCompleteJob = () => {
    mutators.updateComponentStyles({ componentName, toStyles });
    mutators.countdownAnimations({ animationName });
  }

  mutators.registerTimedJob({ index, componentName, job });
  mutators.registerTimedOnCompletedJob({ index, componentName, onCompleteJob });
  mutators.startTimedJob({ index, componentName });
};

const runSpringAnimation = (state, mutators) => (animationName, componentName, animation, index) => {
  const {
    from: fromStyles,
    to: toStyles,
    stiffness,
    damping,
  } = animation;
  let startTime;
  if (!IS_PRODUCTION) {
    startTime = mutators.now();
  }

  mutators.createSpringMachine({
    index,
    componentName,
    fromStyles,
    toStyles,
    stiffness,
    damping,
  });
  mutators.createEndlessJobMachine({ index, componentName });

  if (!IS_PRODUCTION) {
    Recorder.reset(animationName);
  }

  const onNext = updatedStyles => {
    mutators.updateComponentStyles({ componentName, updatedStyles });

    if (!IS_PRODUCTION) {
      const updateStyles = state.styleUpdaters[componentName];
      const elapsedTime = mutators.now() - startTime;
      Recorder.record({ animationName, componentName, elapsedTime, updatedStyles, updateStyles });
    }
  }

  const onComplete = updatedStyles => {
    mutators.updateComponentStyles({ componentName, updatedStyles });
    mutators.stopEndlessJobMachine({ index, componentName });
    mutators.countdownAnimations({ animationName });
  }

  const job = () => {
    mutators.runNextSpringFrame({ index, componentName, onNext, onComplete });
  }

  mutators.registerEndlessJob({ index, componentName, job });
  mutators.startEndlessJobMachine({ index, componentName });
};

export const play = (state, mutators) => (animationName, onComplete) => {
  // IMPROVE: It's weird that getNumPhases calls makeSequence internally, but
  // things are the way they are so the user does not need to write each
  // phase as a function - having this inefficiency makes the API nicer.
  const numPhases = getNumPhases(state)(animationName);

  if (!IS_PRODUCTION) {
    if (numPhases === 0) {
      throw makeError(
        `Attemped to run an empty animation sequence. Check <Animatronics/>`
        + ` or withAnimatronics and make sure youre're returning either an Array or`
        + ` an Object from the "createAnimationSequences" function. Here's what your`
        + ` function looks like right now:`
        + `\n`
        + `${ state.createAnimationSequences.toString() }`
      );
    }
  }

  mutators.createPhasesCountdownMachine({
    animationName,
    job: onComplete,
    numPhases,
  });

  const runPhase = (phaseIndex) => {
    const sequence = makeSequence(state)(animationName);
    const phase = sequence[phaseIndex];
    debug('executing phase %O', phase);
    const componentNames = Object.keys(phase);
    const numAnimations = componentNames.reduce(
      (result, componentNames) => {
        const rawAnimation = phase[componentNames];
        const animations = Array.isArray(rawAnimation) ? rawAnimation : [rawAnimation];
        return result + animations.length;
      },
      0
    );

    const job = () => {
      mutators.countdownPhases({ animationName });
      if (phaseIndex < (numPhases - 1)) {
        runPhase(phaseIndex + 1);
      }
    }

    mutators.createAnimationCountdownMachine({
      animationName,
      job,
      numAnimations,
    });

    componentNames.forEach(componentName => {
      const rawAnimation = phase[componentName];
      const animations = Array.isArray(rawAnimation) ? rawAnimation : [rawAnimation];

      const runAnimation = (animation, index) => {
        if (isUsingTime(animation)) {
          runTimedAnimation(state, mutators)(animationName, componentName, animation, index);
        } else if (isUsingSpring(animation)) {
          runSpringAnimation(state, mutators)(animationName, componentName, animation, index);
        }
      };

      animations.forEach((animation, index) => {
        const { delay } = animation;
        if (delay == null) {
          runAnimation(animation, index);
        } else {
          mutators.runDelayedAnimation({ componentName, delay, runAnimation, animation, index });
        }
      });
    });
  }

  runPhase(0);
};

const stop = (state, mutators) => () => {
  mutators.stopMachine();
};

const reset = (state, mutators) => () => {
  reducer.stopMachine();
  reducer.resetMachine();
};

const registerComponent = (state, mutators) => (componentName, node, styleUpdater, styleResetter) => {
  mutators.registerComponent({
    componentName,
    node,
    styleUpdater,
    styleResetter,
  });
}

const unregisterComponent = (state, mutators) => componentName => {
  mutators.unregisterComponent({ componentName });
}

const setCreateAnimationSequences = (state, mutators) => createAnimationSequences => {
  mutators.setCreateAnimationSequences({
    createAnimationSequences,
  });
}

// EXPERIMENT: Isolating any shared state mutations here.
export const makeMutators = (machinist, state) => ({

  createSpringMachine: action => {
    const {
      index,
      componentName,
      fromStyles,
      toStyles,
      stiffness,
      damping,
    } = action;
    const machine = machinist.makeSpringMachine(
      fromStyles,
      toStyles,
      stiffness,
      damping
    );
    if (!state.springMachines[componentName]) {
      state.springMachines[componentName] = [];
    }
    state.springMachines[componentName][index] = machine;
  },

  createEndlessJobMachine: action => {
    const { index, componentName } = action;
    const machine = machinist.makeEndlessJobMachine(
      machinist.requestAnimationFrame,
      machinist.cancelAnimationFrame
    );
    if (!state.endlessJobMachines[componentName]) {
      state.endlessJobMachines[componentName] = [];
    }
    state.endlessJobMachines[componentName][index] = machine;
  },

  createAnimationCountdownMachine: action => {
    const { numAnimations, job, animationName } = action;
    const machine = machinist.makeCountdownJobMachine(numAnimations);
    machine.registerJob(job);
    state.animationCountdownMachines[animationName] = machine;
  },

  createPhasesCountdownMachine: action => {
    const { numPhases, job, animationName } = action;
    const machine = machinist.makeCountdownJobMachine(numPhases);
    machine.registerJob(job);
    state.phasesCountdownMachines[animationName] = machine;
  },

  createTimedJobMachine: action => {
    const { index, componentName, duration } = action;
    const machine = machinist.makeTimedJobMachine(
      duration,
      machinist.requestAnimationFrame,
      machinist.cancelAnimationFrame,
      machinist.now,
    );
    if (!state.timedJobMachines[componentName]) {
      state.timedJobMachines[componentName] = [];
    }
    state.timedJobMachines[componentName][index] = machine;
  },

  countdownAnimations: action => {
    const { animationName } = action;
    state.animationCountdownMachines[animationName].countdown();
  },

  countdownPhases: action => {
    const { animationName } = action;
    state.phasesCountdownMachines[animationName].countdown();
  },

  now: () => {
    return machinist.now();
  },

  registerComponent: action => {
    const { componentName, node, styleUpdater, styleResetter } = action;
    debug('registering component %s %o', componentName, node);
    state.nodes[componentName] = node;
    state.styleUpdaters[componentName] = styleUpdater;
    state.styleResetters[componentName] = styleResetter;
  },

  registerEndlessJob: action => {
    const { index, componentName, job } = action;
    state.endlessJobMachines[componentName][index].registerJob(job);
  },

  registerTimedJob: action => {
    const { index, componentName, job } = action;
    state.timedJobMachines[componentName][index].registerJob(job);
  },

  registerTimedOnCompletedJob: action => {
    const { index, componentName, onCompleteJob } = action;
    state.timedJobMachines[componentName][index].registerOnCompleteJob(onCompleteJob);
  },

  resetMachine: action => {
    Object.keys(state.styleResetters).map(componentName => {
      state.styleResetters[componentName]();
    });
  },

  runNextSpringFrame: action => {
    const {
      index,
      componentName,
      onNext,
      onComplete,
    } = action;
    state.springMachines[componentName][index].runNextFrame(onNext, onComplete);
  },

  runDelayedAnimation: action => {
    const { componentName, delay, runAnimation, animation, index } = action;
    state.timeouts[componentName] = machinist.setTimeout(
      () => runAnimation(animation, index),
      delay
    );
  },

  setCreateAnimationSequences: action => {
    const { createAnimationSequences } = action;
    debug('setting updated createAnimationSequences %s', createAnimationSequences);
    state.createAnimationSequences = createAnimationSequences;
  },

  startTimedJob: action => {
    const { index, componentName } = action;
    state.timedJobMachines[componentName][index].start();
  },

  startEndlessJobMachine: action => {
    debug('starting endless job machine i.e. spring %O', action);
    const { index, componentName } = action;
    state.endlessJobMachines[componentName][index].start();
  },

  stopMachine: action => {
    flatten(Object.values(state.timedJobMachines))
      .forEach(machine => machine.stop());
    Object.values(state.timeouts)
      .forEach(timeout => machinist.clearTimeout(timeout));
    state.timedJobMachines = {};
    state.timeouts = {};
  },

  stopEndlessJobMachine: action => {
    const { index, componentName } = action;
    state.endlessJobMachines[componentName][index].stop();
  },

  unregisterComponent: action => {
    const { componentName } = action;
    delete state.nodes[componentName];
    delete state.styleUpdaters[componentName];
  },

  updateComponentStyles: action => {
    const { componentName, updatedStyles } = action;
    const updateStyles = state.styleUpdaters[componentName];
    if (updateStyles) {
      updateStyles(updatedStyles);
    }
  }
});

export const makeAnimatronicsMachine = machinist => createAnimationSequences => {
  const state = {
    nodes: {},
    styleUpdaters: {},
    styleResetters: {},
    timedJobMachines: {},
    springMachines: {},
    endlessJobMachines: {},
    timeouts: {},
    animationCountdownMachines: {},
    phasesCountdownMachines: {},
    createAnimationSequences,
  };

  const mutators = makeMutators(machinist, state);

  const animatronicsMachine = {
    play: play(state, mutators),
    stop: stop(state, mutators),
    reset: reset(state, mutators),
    registerComponent: registerComponent(state, mutators),
    unregisterComponent: unregisterComponent(state, mutators),
    setCreateAnimationSequences: setCreateAnimationSequences(state, mutators),
  };

  return animatronicsMachine;
}
<|endoftext|>