<commit_before>import Renderer from "./renderer.js";
import GenomeBrowserUtils from "../genome-browser-utils.js";
import {SVG} from "../../core/svg.js";
import LollipopLayout from "../../core/visualisation/lollipop-layout.js";

export default class VariantRenderer extends Renderer {

    constructor(config) {
        super(config);

        this.lollipopLayout = new LollipopLayout({
            minSeparation: this.config.lollipopMaxWidth,
        });
    }

    render(features, options) {
        const lollipopRegionWidth = options.requestedRegion.length() * options.pixelBase;
        const lollipopStartX = GenomeBrowserUtils.getFeatureX(options.requestedRegion.start, options);
        const lollipopStickHeight = this.config.lollipopHeight - this.config.lollipopFocusWidth - this.config.lollipopMaxWidth / 2;
        let lollipopStickStart = this.config.lollipopFocusWidth + this.config.lollipopMaxWidth / 2;
        let lollipopPositions = [];
        let topPosition = this.config.lollipopVisible ? this.config.lollipopHeight : this.config.headerHeight;

        if (this.config.lollipopVisible) {
            lollipopPositions = this.lollipopLayout.layout(features || [], options.requestedRegion, lollipopRegionWidth);
        }

        // Check if highlights are visible
        if (this.config.highlightVisible) {
            topPosition = topPosition + this.config.highlightHeight;
            lollipopStickStart = lollipopStickStart + this.config.highlightHeight;
        }

        (features || []).forEach((feature, featureIndex) => {
            const group = SVG.addChild(options.svgCanvasFeatures, "g", {});

            // get feature genomic information
            const start = feature.start;
            const end = feature.end;
            const length = Math.max(Math.abs((end - start) + 1), 1);

            // transform to pixel position
            const width = Math.max(length * options.pixelBase, 1);
            const x = GenomeBrowserUtils.getFeatureX(start, options);
            const center = GenomeBrowserUtils.getFeatureX(start + length / 2, options);

            // Get variant information
            const variantColor = this.getValueFromConfig("variantColor", [feature, this.config.sampleNames]);
            const variantTooltipTitle = this.getValueFromConfig("variantTooltipTitle", [feature, this.config.sampleNames]);
            const variantTooltipText = this.getValueFromConfig("variantTooltipText", [feature, this.config.sampleNames]);

            let variantElement = null;

            // Check if lollipops are visible
            if (this.config.lollipopVisible) {
                const lollipopX = lollipopStartX + lollipopPositions[featureIndex];
                const lollipopWidth = Math.min(1, Math.max(0, this.getValueFromConfig("lollipopWidth", [feature])));
                const lollipopPath = [
                    `M ${lollipopX},${lollipopStickStart}`,
                    `L ${lollipopX},${lollipopStickStart + lollipopStickHeight / 2}`,
                    `L ${center},${lollipopStickStart + 3 * lollipopStickHeight / 4}`,
                    `L ${center},${lollipopStickStart + lollipopStickHeight}`,
                ];

                // Render lollipop stick
                const lollipopStick = SVG.addChild(group, "path", {
                    "d": lollipopPath.join(" "),
                    "fill": "transparent",
                    "stroke": this.config.lollipopStickColor,
                    "stroke-width": this.config.lollipopStickWidth,
                });

                // Lollipop shape
                variantElement = this.getValueFromConfig("lollipopShape", [
                    feature,
                    group,
                    lollipopX,
                    lollipopStickStart,
                    this.config.lollipopMinWidth + lollipopWidth * (this.config.lollipopMaxWidth - this.config.lollipopMinWidth),
                    variantColor,
                ]);

                // Register lollipop focus events
                if (this.config.lollipopFocusEnabled) {
                    variantElement.addEventListener("mouseenter", () => {
                        lollipopStick.setAttribute("stroke", this.config.lollipopFocusColor);
                        lollipopStick.setAttribute("stroke-width", this.config.lollipopFocusWidth);
                        variantElement.setAttribute("stroke", this.config.lollipopFocusColor);
                        variantElement.setAttribute("stroke-width", this.config.lollipopFocusWidth);
                    });
                    variantElement.addEventListener("mouseleave", () => {
                        lollipopStick.setAttribute("stroke", this.config.lollipopStickColor);
                        lollipopStick.setAttribute("stroke-width", this.config.lollipopStickWidth);
                        variantElement.setAttribute("stroke-width", 0);
                    });
                }
            } else {
                variantElement = SVG.addChild(group, "rect", {
                    "x": x,
                    "y": 0,
                    "width": `${width}px`,
                    "height": `${this.config.headerHeight - this.config.dividerHeight - 1}px`,
                    "fill": variantColor,
                    "cursor": "pointer",
                });
            }

            // Display tooltip
            if (variantElement && variantTooltipText) {
                $(variantElement).qtip({
                    content: {
                        text: variantTooltipText,
                        title: variantTooltipTitle || null,
                    },
                    position: {
                        viewport: window,
                        target: "mouse",
                    },
                    style: {
                        width: true,
                        classes: `${this.config.toolTipfontClass} ui-tooltip ui-tooltip-shadow`,
                    },
                });
            }

            // Check if highlights are visible
            if (this.config.highlightVisible && this.config.highlights?.length) {
                const satisfiedHighlights = this.config.highlights.filter(item => {
                    return !!item.condition(feature);
                });

                if (satisfiedHighlights.length > 0) {
                    const highlightText = satisfiedHighlights.map(item => {
                        return `

${item.name || item.id || "-"}

${item.description || "-"}

                        `;
                    });
                    const iconCenterX = this.config.lollipopVisible ? lollipopStartX + lollipopPositions[featureIndex] : center;
                    const iconCenterY = this.config.highlightHeight / 2;
                    const iconHalf = this.config.highlightIconSize / 2;
                    const iconPath = [
                        `M${iconCenterX},${iconCenterY} l0,${iconHalf}`,
                        `M${iconCenterX},${iconCenterY} l0,-${iconHalf}`,
                        `M${iconCenterX},${iconCenterY} l${iconHalf},${iconHalf / 2}`,
                        `M${iconCenterX},${iconCenterY} l${iconHalf},-${iconHalf / 2}`,
                        `M${iconCenterX},${iconCenterY} l-${iconHalf},${iconHalf / 2}`,
                        `M${iconCenterX},${iconCenterY} l-${iconHalf},-${iconHalf / 2}`,
                    ];

                    // Create highlight icon
                    SVG.addChild(group, "path", {
                        "d": iconPath.join(" "),
                        "fill": "transparent",
                        "stroke-width": this.config.highlightIconWidth,
                        "stroke": this.config.highlightIconColor,
                    });

                    // Mask for displaying tooltip with the highlight info
                    const highlightMaskElement = SVG.addChild(group, "rect", {
                        x: iconCenterX - this.config.highlightIconSize / 2,
                        y: 0,
                        width: this.config.highlightIconSize,
                        height: this.config.highlightHeight,
                        fill: "transparent",
                        stroke: "transparent",
                    });
                    $(highlightMaskElement).qtip({
                        content: {
                            text: highlightText.join(""),
                            title: feature.id || null,
                        },
                        position: {
                            viewport: window,
                            target: "mouse",
                        },
                        style: {
                            width: true,
                            classes: `${this.config.toolTipfontClass} ui-tooltip ui-tooltip-shadow`,
                        },
                    });
                }
            }

            // Render for each sample in feature.studies[0].samples
            // this.config.sampleNames.forEach((sampleName, index) => {
            (feature.studies[0]?.samples || []).forEach((sampleData, index) => {
                // Only one study is expected, and GT is always the first field in samplesData
                const genotype = sampleData.data[0];
                const sampleGenotypeColor = this.getValueFromConfig("sampleGenotypeColor", [genotype]);
                const sampleGenotypeTooltipTitle = this.getValueFromConfig("sampleGenotypeTooltipTitle", [feature, sampleData]);
                const sampleGenotypeTooltipText = this.getValueFromConfig("sampleGenotypeTooltipText", [feature, sampleData]);

                const sampleGenotypeElement = SVG.addChild(group, "rect", {
                    "x": x,
                    "y": topPosition + (index * this.config.sampleHeight) + 1,
                    "width": width,
                    "height": this.config.sampleHeight - 2,
                    // "stroke": "#000000",
                    // "stroke-width": 1,
                    // "stroke-opacity": 0.7,
                    "fill": sampleGenotypeColor,
                    "cursor": "pointer",
                    // "data-genotype": genotype,
                });

                if (sampleGenotypeTooltipText) {
                    $(sampleGenotypeElement).qtip({
                        content: {
                            text: sampleGenotypeTooltipText,
                            title: sampleGenotypeTooltipTitle || null,
                        },
                        position: {
                            viewport: window,
                            target: "mouse",
                        },
                        style: {
                            width: true,
                            classes: `${this.config.toolTipfontClass} ui-tooltip ui-tooltip-shadow`,
                        },
                    });
                }
            });
        });
    }

    getDefaultConfig() {
        return {
            infoWidgetId: "id",
            strokeColor: "#555",
            height: 10,
            histogramColor: "#58f3f0",
            // label: GenomeBrowserUtils.variantLabelFormatter,
            variantColor: GenomeBrowserUtils.variantColorFormatter,
            variantTooltipTitle: GenomeBrowserUtils.variantTooltipTitleFormatter,
            variantTooltipText: GenomeBrowserUtils.variantTooltipTextFormatter,
            sampleGenotypeColor: GenomeBrowserUtils.genotypeColorFormatter,
            sampleGenotypeTooltipTitle: GenomeBrowserUtils.sampleGenotypeTooltipTitleFormatter,
            sampleGenotypeTooltipText: GenomeBrowserUtils.sampleGenotypeTooltipTextFormatter,
            // sampleTrackHeight: 20,
            // sampleTrackY: 15,
            // sampleTrackFontSize: 12,
            sampleNames: [],
            sampleHeight: 40,
            // Header configuration
            headerHeight: 20,
            dividerHeight: 2,
            // Lollipop configuration
            lollipopVisible: true,
            lollipopHeight: 40,
            lollipopStickColor: "rgb(164,171,182)",
            lollipopStickWidth: 1,
            lollipopMinWidth: 10,
            lollipopMaxWidth: 15,
            lollipopShape: GenomeBrowserUtils.lollipopShapeFormatter,
            lollipopWidth: GenomeBrowserUtils.lollipopWidthFormatter,
            // Lollipop focus
            lollipopFocusEnabled: true,
            lollipopFocusWidth: 2,
            lollipopFocusColor: "orange",
            // Highlights
            highlights: [],
            highlightVisible: true,
            highlightHeight: 16,
            highlightIconColor: "red",
            highlightIconSize: 6,
            highlightIconWidth: 1,
        };
    }

}
<commit_msg>gb: minor fixes in variant tooltip position #TASK-940<commit_after>import Renderer from "./renderer.js";
import GenomeBrowserUtils from "../genome-browser-utils.js";
import {SVG} from "../../core/svg.js";
import LollipopLayout from "../../core/visualisation/lollipop-layout.js";

export default class VariantRenderer extends Renderer {

    constructor(config) {
        super(config);

        this.lollipopLayout = new LollipopLayout({
            minSeparation: this.config.lollipopMaxWidth,
        });
    }

    render(features, options) {
        const lollipopRegionWidth = options.requestedRegion.length() * options.pixelBase;
        const lollipopStartX = GenomeBrowserUtils.getFeatureX(options.requestedRegion.start, options);
        const lollipopStickHeight = this.config.lollipopHeight - this.config.lollipopFocusWidth - this.config.lollipopMaxWidth / 2;
        let lollipopStickStart = this.config.lollipopFocusWidth + this.config.lollipopMaxWidth / 2;
        let lollipopPositions = [];
        let topPosition = this.config.lollipopVisible ? this.config.lollipopHeight : this.config.headerHeight;

        if (this.config.lollipopVisible) {
            lollipopPositions = this.lollipopLayout.layout(features || [], options.requestedRegion, lollipopRegionWidth);
        }

        // Check if highlights are visible
        if (this.config.highlightVisible) {
            topPosition = topPosition + this.config.highlightHeight;
            lollipopStickStart = lollipopStickStart + this.config.highlightHeight;
        }

        (features || []).forEach((feature, featureIndex) => {
            const group = SVG.addChild(options.svgCanvasFeatures, "g", {});

            // get feature genomic information
            const start = feature.start;
            const end = feature.end;
            const length = Math.max(Math.abs((end - start) + 1), 1);

            // transform to pixel position
            const width = Math.max(length * options.pixelBase, 1);
            const x = GenomeBrowserUtils.getFeatureX(start, options);
            const center = GenomeBrowserUtils.getFeatureX(start + length / 2, options);

            // Get variant information
            const variantColor = this.getValueFromConfig("variantColor", [feature, this.config.sampleNames]);
            const variantTooltipTitle = this.getValueFromConfig("variantTooltipTitle", [feature, this.config.sampleNames]);
            const variantTooltipText = this.getValueFromConfig("variantTooltipText", [feature, this.config.sampleNames]);

            let variantElement = null;

            // Check if lollipops are visible
            if (this.config.lollipopVisible) {
                const lollipopX = lollipopStartX + lollipopPositions[featureIndex];
                const lollipopWidth = Math.min(1, Math.max(0, this.getValueFromConfig("lollipopWidth", [feature])));
                const lollipopPath = [
                    `M ${lollipopX},${lollipopStickStart}`,
                    `L ${lollipopX},${lollipopStickStart + lollipopStickHeight / 2}`,
                    `L ${center},${lollipopStickStart + 3 * lollipopStickHeight / 4}`,
                    `L ${center},${lollipopStickStart + lollipopStickHeight}`,
                ];

                // Render lollipop stick
                const lollipopStick = SVG.addChild(group, "path", {
                    "d": lollipopPath.join(" "),
                    "fill": "transparent",
                    "stroke": this.config.lollipopStickColor,
                    "stroke-width": this.config.lollipopStickWidth,
                });

                // Lollipop shape
                variantElement = this.getValueFromConfig("lollipopShape", [
                    feature,
                    group,
                    lollipopX,
                    lollipopStickStart,
                    this.config.lollipopMinWidth + lollipopWidth * (this.config.lollipopMaxWidth - this.config.lollipopMinWidth),
                    variantColor,
                ]);

                // Register lollipop focus events
                if (this.config.lollipopFocusEnabled) {
                    variantElement.addEventListener("mouseenter", () => {
                        lollipopStick.setAttribute("stroke", this.config.lollipopFocusColor);
                        lollipopStick.setAttribute("stroke-width", this.config.lollipopFocusWidth);
                        variantElement.setAttribute("stroke", this.config.lollipopFocusColor);
                        variantElement.setAttribute("stroke-width", this.config.lollipopFocusWidth);
                    });
                    variantElement.addEventListener("mouseleave", () => {
                        lollipopStick.setAttribute("stroke", this.config.lollipopStickColor);
                        lollipopStick.setAttribute("stroke-width", this.config.lollipopStickWidth);
                        variantElement.setAttribute("stroke-width", 0);
                    });
                }
            } else {
                variantElement = SVG.addChild(group, "rect", {
                    "x": x,
                    "y": 0,
                    "width": `${width}px`,
                    "height": `${this.config.headerHeight - this.config.dividerHeight - 1}px`,
                    "fill": variantColor,
                    "cursor": "pointer",
                });
            }

            // Display tooltip
            if (variantElement && variantTooltipText) {
                $(variantElement).qtip({
                    content: {
                        text: variantTooltipText,
                        title: variantTooltipTitle || null,
                    },
                    position: {
                        viewport: window,
                        target: "mouse",
                        adjust: {x: 10, y: 10},
                    },
                    style: {
                        width: true,
                        classes: `${this.config.toolTipfontClass} ui-tooltip ui-tooltip-shadow`,
                    },
                });
            }

            // Check if highlights are visible
            if (this.config.highlightVisible && this.config.highlights?.length) {
                const satisfiedHighlights = this.config.highlights.filter(item => {
                    return !!item.condition(feature);
                });

                if (satisfiedHighlights.length > 0) {
                    const highlightText = satisfiedHighlights.map(item => {
                        return `

${item.name || item.id || "-"}

${item.description || "-"}

                        `;
                    });
                    const iconCenterX = this.config.lollipopVisible ? lollipopStartX + lollipopPositions[featureIndex] : center;
                    const iconCenterY = this.config.highlightHeight / 2;
                    const iconHalf = this.config.highlightIconSize / 2;
                    const iconPath = [
                        `M${iconCenterX},${iconCenterY} l0,${iconHalf}`,
                        `M${iconCenterX},${iconCenterY} l0,-${iconHalf}`,
                        `M${iconCenterX},${iconCenterY} l${iconHalf},${iconHalf / 2}`,
                        `M${iconCenterX},${iconCenterY} l${iconHalf},-${iconHalf / 2}`,
                        `M${iconCenterX},${iconCenterY} l-${iconHalf},${iconHalf / 2}`,
                        `M${iconCenterX},${iconCenterY} l-${iconHalf},-${iconHalf / 2}`,
                    ];

                    // Create highlight icon
                    SVG.addChild(group, "path", {
                        "d": iconPath.join(" "),
                        "fill": "transparent",
                        "stroke-width": this.config.highlightIconWidth,
                        "stroke": this.config.highlightIconColor,
                    });

                    // Mask for displaying tooltip with the highlight info
                    const highlightMaskElement = SVG.addChild(group, "rect", {
                        x: iconCenterX - this.config.highlightIconSize / 2,
                        y: 0,
                        width: this.config.highlightIconSize,
                        height: this.config.highlightHeight,
                        fill: "transparent",
                        stroke: "transparent",
                    });
                    $(highlightMaskElement).qtip({
                        content: {
                            text: highlightText.join(""),
                            title: feature.id || null,
                        },
                        position: {
                            viewport: window,
                            target: "mouse",
                        },
                        style: {
                            width: true,
                            classes: `${this.config.toolTipfontClass} ui-tooltip ui-tooltip-shadow`,
                        },
                    });
                }
            }

            // Render for each sample in feature.studies[0].samples
            // this.config.sampleNames.forEach((sampleName, index) => {
            (feature.studies[0]?.samples || []).forEach((sampleData, index) => {
                // Only one study is expected, and GT is always the first field in samplesData
                const genotype = sampleData.data[0];
                const sampleGenotypeColor = this.getValueFromConfig("sampleGenotypeColor", [genotype]);
                const sampleGenotypeTooltipTitle = this.getValueFromConfig("sampleGenotypeTooltipTitle", [feature, sampleData]);
                const sampleGenotypeTooltipText = this.getValueFromConfig("sampleGenotypeTooltipText", [feature, sampleData]);

                const sampleGenotypeElement = SVG.addChild(group, "rect", {
                    "x": x,
                    "y": topPosition + (index * this.config.sampleHeight) + 1,
                    "width": width,
                    "height": this.config.sampleHeight - 2,
                    // "stroke": "#000000",
                    // "stroke-width": 1,
                    // "stroke-opacity": 0.7,
                    "fill": sampleGenotypeColor,
                    "cursor": "pointer",
                    // "data-genotype": genotype,
                });

                if (sampleGenotypeTooltipText) {
                    $(sampleGenotypeElement).qtip({
                        content: {
                            text: sampleGenotypeTooltipText,
                            title: sampleGenotypeTooltipTitle || null,
                        },
                        position: {
                            viewport: window,
                            target: "mouse",
                        },
                        style: {
                            width: true,
                            classes: `${this.config.toolTipfontClass} ui-tooltip ui-tooltip-shadow`,
                        },
                    });
                }
            });
        });
    }

    getDefaultConfig() {
        return {
            infoWidgetId: "id",
            strokeColor: "#555",
            height: 10,
            histogramColor: "#58f3f0",
            // label: GenomeBrowserUtils.variantLabelFormatter,
            variantColor: GenomeBrowserUtils.variantColorFormatter,
            variantTooltipTitle: GenomeBrowserUtils.variantTooltipTitleFormatter,
            variantTooltipText: GenomeBrowserUtils.variantTooltipTextFormatter,
            sampleGenotypeColor: GenomeBrowserUtils.genotypeColorFormatter,
            sampleGenotypeTooltipTitle: GenomeBrowserUtils.sampleGenotypeTooltipTitleFormatter,
            sampleGenotypeTooltipText: GenomeBrowserUtils.sampleGenotypeTooltipTextFormatter,
            // sampleTrackHeight: 20,
            // sampleTrackY: 15,
            // sampleTrackFontSize: 12,
            sampleNames: [],
            sampleHeight: 40,
            // Header configuration
            headerHeight: 20,
            dividerHeight: 2,
            // Lollipop configuration
            lollipopVisible: true,
            lollipopHeight: 40,
            lollipopStickColor: "rgb(164,171,182)",
            lollipopStickWidth: 1,
            lollipopMinWidth: 10,
            lollipopMaxWidth: 15,
            lollipopShape: GenomeBrowserUtils.lollipopShapeFormatter,
            lollipopWidth: GenomeBrowserUtils.lollipopWidthFormatter,
            // Lollipop focus
            lollipopFocusEnabled: true,
            lollipopFocusWidth: 2,
            lollipopFocusColor: "orange",
            // Highlights
            highlights: [],
            highlightVisible: true,
            highlightHeight: 16,
            highlightIconColor: "red",
            highlightIconSize: 6,
            highlightIconWidth: 1,
        };
    }

}
<|endoftext|>


<commit_before>'use strict';

const {
  GraphQLObjectType,
  GraphQLInputObjectType,
  GraphQLList,
  GraphQLInt,
  GraphQLFloat,
  GraphQLBoolean,
  GraphQLString,
  GraphQLNonNull,
} = require('graphql');
const { chain, omit, pick } = require('lodash');
const { stringify } = require('circular-json');
const uuidv4 = require('uuid/v4');

const { EngineError } = require('../../../../error');
const { memoize } = require('../../../../utilities');
const { getTypeName } = require('./name');
const { getSubDef, isModel } = require('./utilities');
const { getArguments } = require('./arguments');

// Retrieves the GraphQL type for a given IDL definition
const getType = function (def, opts = {}) {
  return getField(def, opts).type;
};

// Retrieves a GraphQL field info for a given IDL definition, i.e. an object that can be passed to new
// GraphQLObjectType({ fields })
// Includes return type, resolve function, arguments, etc.
const getField = function (def, opts) {
  opts.inputObjectType = opts.inputObjectType || '';

  const fieldGetter = graphQLFieldGetters.find(possibleType => possibleType.condition(def, opts));
  if (!fieldGetter) {
    throw new EngineError(`Could not parse property into a GraphQL type: ${stringify(def)}`, {
      reason: 'GRAPHQL_WRONG_DEFINITION',
    });
  }

  let { type, args } = fieldGetter.value(def, opts);

  // Fields description|deprecation_reason are taken from IDL definition
  const description = def.description;
  const deprecationReason = def.deprecated;

	// Only for models, and not for argument types
  // Modifiers (Array and NonNull) retrieve their arguments from underlying type (i.e. `args` is already defined)
  if (isModel(def) && opts.inputObjectType === '' && !args) {
    args = getArguments(def, Object.assign(opts, { getType }));
  }

  // Can only assign default if fields are optional in input, but required by database
  let defaultValue;
  if (canRequireAttributes(def, opts) && !opts.isRequired && opts.inputObjectType === 'data') {
    defaultValue = def.default;
  }

  const field = { type, description, deprecationReason, args, defaultValue };
  return field;
};

// Required field FieldGetter
const graphQLRequiredFieldGetter = function (def, opts) {
  // Goal is to avoid infinite recursion, i.e. without modification the same graphQLFieldGetter would be hit again
  opts = Object.assign({}, opts, { isRequired: false });
  const { type: subType, args } = getField(def, opts);
  const type = new GraphQLNonNull(subType);
  return { type, args };
};

// Array field FieldGetter
const graphQLArrayFieldGetter = function (def, opts) {
  const subDef = getSubDef(def);
  const { type: subType, args } = getField(subDef, opts);
  const type = new GraphQLList(subType);
  return { type, args };
};

/**
 * Memoize object type constructor in order to infinite recursion.
 * We use the type name, i.e.:
 *  - type name must differ everytime type might differ
 *  - in particular, at the moment, type name differ when inputObjectType, opType or multiple changes
 * We also namespace with a UUID which is unique for each new call to `getSchema()`, to avoid leaking
 **/
const objectTypeSerializer = function ([ def, opts ]) {
  const typeName = getTypeName({ def, opts });
  opts.schemaId = opts.schemaId || uuidv4();
  return `${opts.schemaId}/${typeName}`;
};

// Object field FieldGetter
const graphQLObjectFieldGetter = memoize(function (def, opts) {
  const name = getTypeName({ def, opts });
  const description = def.description;
	const constructor = opts.inputObjectType !== '' ? GraphQLInputObjectType : GraphQLObjectType;
  const fields = getObjectFields(def, opts);

  const type = new constructor({ name, description, fields });
  return { type };
}, { serializer: objectTypeSerializer });

const filterOpTypes = ['find', 'delete', 'update'];
// Retrieve the fields of an object, using IDL definition
const getObjectFields = function (def, opts) {
  const { operation = {}, inputObjectType } = opts;
  const { opType, multiple } = operation;
  // This needs to be function, otherwise we run in an infinite recursion, if the children try to reference a parent type
  return () => chain(def.properties)
		.omitBy((childDef, childDefName) => {
      const subDef = getSubDef(childDef);
      // Remove all return value fields for delete operations, except the recursive ones and `id`
      // And except for inputObject, since we use it to get the delete filters
      return (opType === 'delete' && !isModel(subDef) && childDefName !== 'id' && inputObjectType !== 'filter')
        // Create operations do not include data.id
        || (opType === 'create' && childDefName === 'id' && inputObjectType === 'data')
        // Filter inputObjects for single operations only include `id`
        || (filterOpTypes.includes(opType) && childDefName !== 'id' && inputObjectType === 'filter' && !multiple);
    })
    // Model-related fields in input|filter arguments must be simple ids, not recursive definition
    .mapValues(childDef => {
      const subDef = getSubDef(childDef);
      if (!(isModel(subDef) && inputObjectType !== '')) { return childDef; }

      const multiple = childDef.operation.multiple;

      // Retrieves `id` field definition of subfield
      const nonRecursiveAttrs = ['description', 'deprecation_reason'];
      const idDef = Object.assign({}, pick(subDef, nonRecursiveAttrs), omit(subDef.properties.id, nonRecursiveAttrs));

      // Assign `id` field definition to e.g. `model.user`
      const idsDef = multiple ? Object.assign({}, childDef, { items: idDef }) : idDef;

      // Consider this attribute as a normal attribute, not a model anymore
      const subIdsDef = getSubDef(idsDef);
      delete subIdsDef.model;

      return idsDef;
    })
		// Recurse over children
		.mapValues((childDef, childDefName) => {
			// if 'Query' or 'Mutation' objects, pass current operation down to sub-fields, and top-level definition
      const childOperation = childDef.operation || operation;
      const childOpts = Object.assign({}, opts, { operation: childOperation });

      childOpts.isRequired = def.required instanceof Array && def.required.includes(childDefName)
        && canRequireAttributes(childDef, childOpts);

			const field = getField(childDef, childOpts);
      return field;
		})
		.value();
};

const canRequireAttributes = function (def, { operation: { opType } = {}, inputObjectType }) {
  // Update operation does not require any attribute in input object
	return !(opType === 'update' && inputObjectType === 'data')
    // Query inputObjects do not require any attribute, except filter.id for single operations
    && inputObjectType !== 'filter';
};

/**
 * Maps an IDL definition into a GraphQL field information, including type
 * The first matching one will be used, i.e. order matters: required modifier, then array modifier come first
 */
const graphQLFieldGetters = [

	// "Required" modifier type
  {
    condition: (def, opts) => opts.isRequired,
    value: graphQLRequiredFieldGetter,
  },

	// "Array" modifier type
  {
    condition: def => def.type === 'array',
    value: graphQLArrayFieldGetter,
  },

	// "Object" type
  {
    condition: def => def.type === 'object',
    value: graphQLObjectFieldGetter,
  },

	// "Int" type
  {
    condition: def => def.type === 'integer',
    value: () => ({ type: GraphQLInt }),
  },

	// "Float" type
  {
    condition: def => def.type === 'number',
    value: () => ({ type: GraphQLFloat }),
  },

	// "String" type
  {
    condition: def => def.type === 'string' || def.type === 'null',
    value: () => ({ type: GraphQLString }),
  },

	// "Boolean" type
  {
    condition: def => def.type === 'boolean',
    value: () => ({ type: GraphQLBoolean }),
  },

];

module.exports = {
  getType,
};
<commit_msg>Simplify syntax<commit_after>'use strict';

const {
  GraphQLObjectType,
  GraphQLInputObjectType,
  GraphQLList,
  GraphQLInt,
  GraphQLFloat,
  GraphQLBoolean,
  GraphQLString,
  GraphQLNonNull,
} = require('graphql');
const { chain, omit, pick } = require('lodash');
const { stringify } = require('circular-json');
const uuidv4 = require('uuid/v4');

const { EngineError } = require('../../../../error');
const { memoize } = require('../../../../utilities');
const { getTypeName } = require('./name');
const { getSubDef, isModel } = require('./utilities');
const { getArguments } = require('./arguments');

// Retrieves the GraphQL type for a given IDL definition
const getType = function (def, opts = {}) {
  return getField(def, opts).type;
};

// Retrieves a GraphQL field info for a given IDL definition, i.e. an object that can be passed to new
// GraphQLObjectType({ fields })
// Includes return type, resolve function, arguments, etc.
const getField = function (def, opts) {
  opts.inputObjectType = opts.inputObjectType || '';

  const fieldGetter = graphQLFieldGetters.find(possibleType => possibleType.condition(def, opts));
  if (!fieldGetter) {
    throw new EngineError(`Could not parse property into a GraphQL type: ${stringify(def)}`, {
      reason: 'GRAPHQL_WRONG_DEFINITION',
    });
  }

  let { type, args } = fieldGetter.value(def, opts);

  // Fields description|deprecation_reason are taken from IDL definition
  const { description, deprecationReason } = def;

	// Only for models, and not for argument types
  // Modifiers (Array and NonNull) retrieve their arguments from underlying type (i.e. `args` is already defined)
  if (isModel(def) && opts.inputObjectType === '' && !args) {
    args = getArguments(def, Object.assign(opts, { getType }));
  }

  // Can only assign default if fields are optional in input, but required by database
  let defaultValue;
  if (canRequireAttributes(def, opts) && !opts.isRequired && opts.inputObjectType === 'data') {
    defaultValue = def.default;
  }

  const field = { type, description, deprecationReason, args, defaultValue };
  return field;
};

// Required field FieldGetter
const graphQLRequiredFieldGetter = function (def, opts) {
  // Goal is to avoid infinite recursion, i.e. without modification the same graphQLFieldGetter would be hit again
  opts = Object.assign({}, opts, { isRequired: false });
  const { type: subType, args } = getField(def, opts);
  const type = new GraphQLNonNull(subType);
  return { type, args };
};

// Array field FieldGetter
const graphQLArrayFieldGetter = function (def, opts) {
  const subDef = getSubDef(def);
  const { type: subType, args } = getField(subDef, opts);
  const type = new GraphQLList(subType);
  return { type, args };
};

/**
 * Memoize object type constructor in order to infinite recursion.
 * We use the type name, i.e.:
 *  - type name must differ everytime type might differ
 *  - in particular, at the moment, type name differ when inputObjectType, opType or multiple changes
 * We also namespace with a UUID which is unique for each new call to `getSchema()`, to avoid leaking
 **/
const objectTypeSerializer = function ([ def, opts ]) {
  const typeName = getTypeName({ def, opts });
  opts.schemaId = opts.schemaId || uuidv4();
  return `${opts.schemaId}/${typeName}`;
};

// Object field FieldGetter
const graphQLObjectFieldGetter = memoize(function (def, opts) {
  const name = getTypeName({ def, opts });
  const description = def.description;
	const constructor = opts.inputObjectType !== '' ? GraphQLInputObjectType : GraphQLObjectType;
  const fields = getObjectFields(def, opts);

  const type = new constructor({ name, description, fields });
  return { type };
}, { serializer: objectTypeSerializer });

const filterOpTypes = ['find', 'delete', 'update'];
// Retrieve the fields of an object, using IDL definition
const getObjectFields = function (def, opts) {
  const { operation = {}, inputObjectType } = opts;
  const { opType, multiple } = operation;
  // This needs to be function, otherwise we run in an infinite recursion, if the children try to reference a parent type
  return () => chain(def.properties)
		.omitBy((childDef, childDefName) => {
      const subDef = getSubDef(childDef);
      // Remove all return value fields for delete operations, except the recursive ones and `id`
      // And except for inputObject, since we use it to get the delete filters
      return (opType === 'delete' && !isModel(subDef) && childDefName !== 'id' && inputObjectType !== 'filter')
        // Create operations do not include data.id
        || (opType === 'create' && childDefName === 'id' && inputObjectType === 'data')
        // Filter inputObjects for single operations only include `id`
        || (filterOpTypes.includes(opType) && childDefName !== 'id' && inputObjectType === 'filter' && !multiple);
    })
    // Model-related fields in input|filter arguments must be simple ids, not recursive definition
    .mapValues(childDef => {
      const subDef = getSubDef(childDef);
      if (!(isModel(subDef) && inputObjectType !== '')) { return childDef; }

      const multiple = childDef.operation.multiple;

      // Retrieves `id` field definition of subfield
      const nonRecursiveAttrs = ['description', 'deprecation_reason'];
      const idDef = Object.assign({}, pick(subDef, nonRecursiveAttrs), omit(subDef.properties.id, nonRecursiveAttrs));

      // Assign `id` field definition to e.g. `model.user`
      const idsDef = multiple ? Object.assign({}, childDef, { items: idDef }) : idDef;

      // Consider this attribute as a normal attribute, not a model anymore
      const subIdsDef = getSubDef(idsDef);
      delete subIdsDef.model;

      return idsDef;
    })
		// Recurse over children
		.mapValues((childDef, childDefName) => {
			// if 'Query' or 'Mutation' objects, pass current operation down to sub-fields, and top-level definition
      const childOperation = childDef.operation || operation;
      const childOpts = Object.assign({}, opts, { operation: childOperation });

      childOpts.isRequired = def.required instanceof Array && def.required.includes(childDefName)
        && canRequireAttributes(childDef, childOpts);

			const field = getField(childDef, childOpts);
      return field;
		})
		.value();
};

const canRequireAttributes = function (def, { operation: { opType } = {}, inputObjectType }) {
  // Update operation does not require any attribute in input object
	return !(opType === 'update' && inputObjectType === 'data')
    // Query inputObjects do not require any attribute, except filter.id for single operations
    && inputObjectType !== 'filter';
};

/**
 * Maps an IDL definition into a GraphQL field information, including type
 * The first matching one will be used, i.e. order matters: required modifier, then array modifier come first
 */
const graphQLFieldGetters = [

	// "Required" modifier type
  {
    condition: (def, opts) => opts.isRequired,
    value: graphQLRequiredFieldGetter,
  },

	// "Array" modifier type
  {
    condition: def => def.type === 'array',
    value: graphQLArrayFieldGetter,
  },

	// "Object" type
  {
    condition: def => def.type === 'object',
    value: graphQLObjectFieldGetter,
  },

	// "Int" type
  {
    condition: def => def.type === 'integer',
    value: () => ({ type: GraphQLInt }),
  },

	// "Float" type
  {
    condition: def => def.type === 'number',
    value: () => ({ type: GraphQLFloat }),
  },

	// "String" type
  {
    condition: def => def.type === 'string' || def.type === 'null',
    value: () => ({ type: GraphQLString }),
  },

	// "Boolean" type
  {
    condition: def => def.type === 'boolean',
    value: () => ({ type: GraphQLBoolean }),
  },

];

module.exports = {
  getType,
};
<|endoftext|>


<commit_before>import React from "react"
import { connect } from "react-redux"
import * as limitOrderActions from "../../actions/limitOrderActions"
import * as utilActions from "../../actions/utilActions"
import * as common from "../../utils/common"
import * as converters from "../../utils/converter"
import { getTranslate } from 'react-localize-redux'
import BLOCKCHAIN_INFO from "../../../../env"
import { ApproveZeroModal, ApproveMaxModal, WrapETHModal, ConfirmModal, SubmitStatusModal, WarningModal } from "./LimitOrderModals"
import { isUserLogin } from "../../utils/common"
import constants from "../../services/constants"
import { Tooltip } from "react-tippy";

@connect((store, props) => {
  const account = store.account.account
  const translate = getTranslate(store.locale)
  const tokens = store.tokens.tokens
  const limitOrder = store.limitOrder
  const ethereum = store.connection.ethereum

  return {
    translate, limitOrder, tokens, account, ethereum,
    global: store.global

  }
})

export default class LimitOrderSubmit extends React.Component {
  constructor() {
    super()
    this.state = {
      isAgree: false
    }
  }

  getUserBalance = () => {
    const tokens = this.props.availableBalanceTokens;
    const srcSymbol = this.props.limitOrder.sourceTokenSymbol;
    const token = common.findTokenBySymbol(tokens, srcSymbol);
    return token.balance;
  }

  getSourceAmount = () => {
    var sourceAmount = parseFloat(this.props.limitOrder.sourceAmount)
    if (isNaN(sourceAmount)) {
      return 0
    }
    this.props.limitOrder.listOrder.map(value => {
      if (value.status === "open" && value.source === this.props.limitOrder.sourceTokenSymbol && value.address.toLowerCase() === this.props.account.address.toLowerCase()) {
        sourceAmount += parseFloat(value.src_amount);
      }
    })
    var sourceAmountBig = converters.toTWei(sourceAmount, this.props.tokens[this.props.limitOrder.sourceTokenSymbol].decimals)
    return sourceAmountBig.toString()
  }

  calculateETHequivalent = () => {
    if (this.props.limitOrder.sourceTokenSymbol === BLOCKCHAIN_INFO.wrapETHToken){
      return this.props.limitOrder.sourceAmount
    }
    if (this.props.limitOrder.destTokenSymbol === BLOCKCHAIN_INFO.wrapETHToken){
      return this.props.limitOrder.destAmount
    }
    var rateBig = converters.toTWei(this.props.tokens[this.props.limitOrder.sourceTokenSymbol].rate, 18)
    var ethEquivalentValue = converters.calculateDest(this.props.limitOrder.sourceAmount, rateBig, 6)
    ethEquivalentValue = converters.toEther(ethEquivalentValue)
    return ethEquivalentValue
  }

  validateOrder = () => {
    // check source amount is zero
    var sourceAmount = parseFloat(this.props.limitOrder.sourceAmount)
    var isValidate = true
    var sourceAmountError = []
    var rateError = []
    if (this.props.limitOrder.sourceTokenSymbol === this.props.limitOrder.destTokenSymbol) {
      sourceAmountError.push("Source token must be different from dest token")
      isValidate = false
    }
    if (isNaN(sourceAmount)) {
      sourceAmountError.push("Source amount is not number")
      isValidate = false
    }

    //verify min source amount
    if (this.props.tokens[this.props.limitOrder.sourceTokenSymbol].rate == 0) {
      sourceAmountError.push("This pair is under maintenance")
      isValidate = false
    }

    // var rateBig = converters.toTWei(this.props.tokens[this.props.limitOrder.sourceTokenSymbol].rate, 18)
    var ethEquivalentValue = this.calculateETHequivalent()

    if (ethEquivalentValue < constants.LIMIT_ORDER_CONFIG.minSupportOrder && !isNaN(sourceAmount)) {
      sourceAmountError.push(`Amount is too smalll. Limit order only support min ${constants.LIMIT_ORDER_CONFIG.minSupportOrder} ETH equivalent order`)
      isValidate = false
    }

    if (ethEquivalentValue > constants.LIMIT_ORDER_CONFIG.maxSupportOrder && !isNaN(sourceAmount)) {
      sourceAmountError.push(`Amount is too big. Limit order only support max ${constants.LIMIT_ORDER_CONFIG.maxSupportOrder} ETH equivalent order`)
      isValidate = false
    }

    // check rate is zero
    var triggerRate = parseFloat(this.props.limitOrder.triggerRate)
    if (isNaN(triggerRate)) {
      rateError.push("Trigger rate is not number")
      isValidate = false
    }
    // check rate is too big
    var triggerRateBig = converters.roundingRate(this.props.limitOrder.triggerRate)
    var percentChange = converters.percentChange(triggerRateBig, this.props.limitOrder.offeredRate)
    if (percentChange > constants.LIMIT_ORDER_CONFIG.maxPercentTriggerRate && !isNaN(triggerRate)) {
      rateError.push(`Trigger rate is too high, only allow ${constants.LIMIT_ORDER_CONFIG.maxPercentTriggerRate}% greater than the current rate`)
      isValidate = false
    }
    if (percentChange < constants.LIMIT_ORDER_CONFIG.minPercentTriggerRate && !isNaN(triggerRate)) {
      rateError.push(`Trigger rate is too low, please increase trigger rate. Minimum rate is 20% lower than current market rate.`)
      isValidate = false
    }

    //check balance
    var userBalance = this.getUserBalance()
    var srcAmount = this.getSourceAmount()
    if (converters.compareTwoNumber(userBalance, srcAmount) < 0) {
      sourceAmountError.push(`Your balance is insufficent for the order. Please check your ${this.props.limitOrder.sourceTokenSymbol} balance and your pending order`)
      isValidate = false
    }

    if (sourceAmountError.length > 0) {
      this.props.dispatch(limitOrderActions.throwError("sourceAmount", sourceAmountError))
    }
    if (rateError.length > 0) {
      this.props.dispatch(limitOrderActions.throwError("triggerRate", rateError))
    }

    if (!isValidate) {
      return
    }

    //check if he is agreed submit order
    console.log("isAgree")
    console.log(this.state.isAgree)
    if (this.state.isAgree) {
      this.findPathOrder()
      this.setState({ isAgree: false })
      return
    }

    // check pair is ok
    var isPedingOrder = false
    for (var i = 0; i < this.props.limitOrder.listOrder.length; i++) {
      var value = this.props.limitOrder.listOrder[i]
      if (value.source === this.props.limitOrder.sourceTokenSymbol && value.dest === this.props.limitOrder.destTokenSymbol
          && value.address.toLowerCase() === this.props.account.address.toLowerCase()) {
        if (converters.compareTwoNumber(this.props.limitOrder.triggerRate, value.min_rate) < 1) {
          isPedingOrder = true
          break
        }
      }
    }
    if (isPedingOrder) {
      if (!this.props.limitOrder.errors.rateWarning) {
        this.props.dispatch(limitOrderActions.throwError("rateWarning", "Lower rate"));
      }
    } else {
      if (this.props.limitOrder.errors.rateWarning) {
        this.props.dispatch(limitOrderActions.throwError("rateWarning", ""));
      }
      this.findPathOrder()
    }

  }

  getMaxGasApprove = () => {
    var tokens = this.props.tokens
    var sourceSymbol = this.props.limitOrder.sourceTokenSymbol
    if (tokens[sourceSymbol] && tokens[sourceSymbol].gasApprove) {
      return tokens[sourceSymbol].gasApprove
    } else {
      return this.props.limitOrder.max_gas_approve
    }
  }

  getMaxGasExchange = () => {
    const tokens = this.props.tokens
    var destTokenSymbol = BLOCKCHAIN_INFO.wrapETHToken
    var destTokenLimit = tokens[destTokenSymbol] && tokens[destTokenSymbol].gasLimit ? tokens[destTokenSymbol].gasLimit : this.props.limitOrder.max_gas

    return destTokenLimit;

  }

  getMaxGasLimit = (orderPath) => {
    var gasLimit = 0
    for (var i = 0; i < orderPath.length; i++) {
      switch (orderPath[i]) {
        case constants.LIMIT_ORDER_CONFIG.orderPath.approveZero:
        case constants.LIMIT_ORDER_CONFIG.orderPath.approveMax:
          gasLimit += this.getMaxGasApprove()
          break
        case constants.LIMIT_ORDER_CONFIG.orderPath.wrapETH:
          gasLimit += this.getMaxGasExchange()
          break
      }
    }
    return gasLimit
  }

  validateBalance = (orderPath) => {
    var gasLimit = this.getMaxGasLimit(orderPath)
    var totalFee = converters.calculateGasFee(this.props.limitOrder.gasPrice, gasLimit)
    var totalFeeBig = converters.toTWei(totalFee, 18)
    var ethBalance = this.props.tokens["ETH"].balance

    var compareValue
    if (this.props.limitOrder.sourceTokenSymbol === BLOCKCHAIN_INFO.wrapETHToken) {
      var srcAmount = this.getSourceAmount()
      var wrapETHTokenBalance = this.props.tokens[BLOCKCHAIN_INFO.wrapETHToken].balance
      compareValue = converters.sumOfTwoNumber(totalFeeBig, converters.subOfTwoNumber(srcAmount, wrapETHTokenBalance))
    } else {
      compareValue = totalFeeBig
    }
    return converters.compareTwoNumber(ethBalance, compareValue) < 0 ? false : true
  }

  async findPathOrder() {
    try {
      var orderPath = []
      // var currentPath = constants.LIMIT_ORDER_CONFIG.orderPath.confirmSubmitOrder
      var ethereum = this.props.ethereum
      // check wrapped eth
      var allowance = await ethereum.call("getAllowanceAtLatestBlock", this.props.limitOrder.sourceToken, this.props.account.address, BLOCKCHAIN_INFO.kyberswapAddress)
      if (allowance == 0) {
        orderPath = [constants.LIMIT_ORDER_CONFIG.orderPath.approveMax]
        // currentPath = constants.LIMIT_ORDER_CONFIG.orderPath.approveMax
      }
      if (allowance != 0 && allowance < Math.pow(10, 28)) {
        orderPath = [constants.LIMIT_ORDER_CONFIG.orderPath.approveZero, constants.LIMIT_ORDER_CONFIG.orderPath.approveMax]
        // currentPath = constants.LIMIT_ORDER_CONFIG.orderPath.approveZero
      }

      if (this.props.limitOrder.sourceTokenSymbol === BLOCKCHAIN_INFO.wrapETHToken) {
        var sourceToken = this.getSourceAmount()
        var userBalance = this.props.tokens[this.props.limitOrder.sourceTokenSymbol].balance
        if (converters.compareTwoNumber(userBalance, sourceToken) < 0) {
          orderPath.push(constants.LIMIT_ORDER_CONFIG.orderPath.wrapETH)
        }
      }
      orderPath.push(constants.LIMIT_ORDER_CONFIG.orderPath.confirmSubmitOrder)
      orderPath.push(constants.LIMIT_ORDER_CONFIG.orderPath.submitStatusOrder)

      //check balance eth is enough

      if (this.validateBalance(orderPath)) {
        this.props.dispatch(limitOrderActions.updateOrderPath(orderPath, 0))
      } else {
        console.log("Your eth balance is not enough for transactions")
        var title = this.props.translate("error.error_occurred") || "Error occurred"
        var content = "Your eth balance is not enough for transactions"
        this.props.dispatch(utilActions.openInfoModal(title, content))
      }

    } catch (err) {
      console.log(err)

      var title = this.props.translate("error.error_occurred") || "Error occurred"
      var content = this.props.translate("error.node_error") || "There are some problems with nodes. Please try again in a while."
      this.props.dispatch(utilActions.openInfoModal(title, content))

    }
  }

  submitOrder = () => {
    if (!isUserLogin()) {
      window.location.href = "/users/sign_in"
    }

    if (this.props.account !== false) {
      this.validateOrder()
    }
  }

  agreeSubmit = () => {
    this.setState({ isAgree: true }, () => {
      this.submitOrder()
    })
  }

  getRateWarningTooltip = () => {
    if (!this.props.account) {
      return null;
    }
    // Filter active orders which have higher rate than current input rate
    const filterHigherRate = this.props.limitOrder.listOrder.filter(item => {
      return item.source === this.props.limitOrder.sourceTokenSymbol &&
            item.dest === this.props.limitOrder.destTokenSymbol &&
            item.address.toLowerCase() === this.props.account.address.toLowerCase() &&
            item.status === "open" &&
            converters.compareTwoNumber(this.props.limitOrder.triggerRate, item.min_rate) < 1;
    });

    const tableComp = filterHigherRate.map(item => {
      const datetime = common.getFormattedDate(item.status === "open" ? item.created_time : item.cancel_time);
      const rate = converters.roundingNumber(item.min_rate);
      return (

{datetime}

{`${item.source.toUpperCase()}/${item.dest.toUpperCase()} >= ${rate}`}

      );
    });

    return (

        {/* Description */}

          {this.props.translate("limit_order.lower_rate_warning") || "This new order has a lower rate than some orders you have created. Below orders will be cancelled when you submitted this order.s"}

        {/* Table */}

          {tableComp}

        {/* Buttons */}

          <button
						className="btn-cancel"
						onClick={e => this.closeRateWarningTooltip()}
					>
						{this.props.translate("limit_order.change_rate") || "Change Rate"}
					</button>
					<button
						className="btn-confirm"
						onClick={e => this.confirmAgreeSubmit(filterHigherRate)}
					>
						{this.props.translate("import.yes") || "Yes"}
					</button>

    );
  }

  closeRateWarningTooltip = () => {
    this.props.dispatch(limitOrderActions.throwError("rateWarning", ""));
  }

  confirmAgreeSubmit = (listPendingCancelOrders) => {
    this.props.dispatch(limitOrderActions.throwError("rateWarning", ""));
    this.props.dispatch(limitOrderActions.setPendingCancelOrders(listPendingCancelOrders));
    this.agreeSubmit();
  }

  render() {
    var isDisable = isUserLogin() && this.props.account == false
    var isWaiting = this.props.limitOrder.isSelectToken || this.props.limitOrder.errors.sourceAmount.length > 0 || this.props.limitOrder.errors.triggerRate.length > 0
    return (

        {(
          <Tooltip
            open={this.props.limitOrder.errors.rateWarning !== ""}
            position="right"
            interactive={true}
            animateFill={false}
            onRequestClose={() => this.closeRateWarningTooltip()}
            html={this.getRateWarningTooltip()}
          >
            <button className={`accept-button ${isDisable ? "disable" : ""} ${isWaiting ? "waiting" : ""}`} onClick={this.submitOrder}>
              {isUserLogin() ? "Submit" : "Login to Submit Order"}
            </button>
          </Tooltip>
        )}

          {this.props.limitOrder.orderPath[this.props.limitOrder.currentPathIndex] === constants.LIMIT_ORDER_CONFIG.orderPath.approveZero && <ApproveZeroModal getMaxGasApprove={this.getMaxGasApprove.bind(this)} />}
          {this.props.limitOrder.orderPath[this.props.limitOrder.currentPathIndex] === constants.LIMIT_ORDER_CONFIG.orderPath.approveMax && <ApproveMaxModal getMaxGasApprove={this.getMaxGasApprove.bind(this)} />}
          {this.props.limitOrder.orderPath[this.props.limitOrder.currentPathIndex] === constants.LIMIT_ORDER_CONFIG.orderPath.wrapETH && <WrapETHModal getOpenOrderAmount={this.props.getOpenOrderAmount} />}
          {this.props.limitOrder.orderPath[this.props.limitOrder.currentPathIndex] === constants.LIMIT_ORDER_CONFIG.orderPath.confirmSubmitOrder && <ConfirmModal />}
          {this.props.limitOrder.orderPath[this.props.limitOrder.currentPathIndex] === constants.LIMIT_ORDER_CONFIG.orderPath.submitStatusOrder && <SubmitStatusModal />}

    )
  }
}
<commit_msg>[Redmine-3559] Filter cancel orders<commit_after>import React from "react"
import { connect } from "react-redux"
import * as limitOrderActions from "../../actions/limitOrderActions"
import * as utilActions from "../../actions/utilActions"
import * as common from "../../utils/common"
import * as converters from "../../utils/converter"
import { getTranslate } from 'react-localize-redux'
import BLOCKCHAIN_INFO from "../../../../env"
import { ApproveZeroModal, ApproveMaxModal, WrapETHModal, ConfirmModal, SubmitStatusModal, WarningModal } from "./LimitOrderModals"
import { isUserLogin } from "../../utils/common"
import constants from "../../services/constants"
import { Tooltip } from "react-tippy";

@connect((store, props) => {
  const account = store.account.account
  const translate = getTranslate(store.locale)
  const tokens = store.tokens.tokens
  const limitOrder = store.limitOrder
  const ethereum = store.connection.ethereum

  return {
    translate, limitOrder, tokens, account, ethereum,
    global: store.global

  }
})

export default class LimitOrderSubmit extends React.Component {
  constructor() {
    super()
    this.state = {
      isAgree: false,
      higherRateOrders: []
    }
  }

  getUserBalance = () => {
    const tokens = this.props.availableBalanceTokens;
    const srcSymbol = this.props.limitOrder.sourceTokenSymbol;
    const token = common.findTokenBySymbol(tokens, srcSymbol);
    return token.balance;
  }

  getSourceAmount = () => {
    var sourceAmount = parseFloat(this.props.limitOrder.sourceAmount)
    if (isNaN(sourceAmount)) {
      return 0
    }
    this.props.limitOrder.listOrder.map(value => {
      if (value.status === constants.LIMIT_ORDER_CONFIG.status.OPEN && value.source === this.props.limitOrder.sourceTokenSymbol && value.address.toLowerCase() === this.props.account.address.toLowerCase()) {
        sourceAmount += parseFloat(value.src_amount);
      }
    })
    var sourceAmountBig = converters.toTWei(sourceAmount, this.props.tokens[this.props.limitOrder.sourceTokenSymbol].decimals)
    return sourceAmountBig.toString()
  }

  calculateETHequivalent = () => {
    if (this.props.limitOrder.sourceTokenSymbol === BLOCKCHAIN_INFO.wrapETHToken){
      return this.props.limitOrder.sourceAmount
    }
    if (this.props.limitOrder.destTokenSymbol === BLOCKCHAIN_INFO.wrapETHToken){
      return this.props.limitOrder.destAmount
    }
    var rateBig = converters.toTWei(this.props.tokens[this.props.limitOrder.sourceTokenSymbol].rate, 18)
    var ethEquivalentValue = converters.calculateDest(this.props.limitOrder.sourceAmount, rateBig, 6)
    ethEquivalentValue = converters.toEther(ethEquivalentValue)
    return ethEquivalentValue
  }

  validateOrder = () => {
    // check source amount is zero
    var sourceAmount = parseFloat(this.props.limitOrder.sourceAmount)
    var isValidate = true
    var sourceAmountError = []
    var rateError = []
    if (this.props.limitOrder.sourceTokenSymbol === this.props.limitOrder.destTokenSymbol) {
      sourceAmountError.push("Source token must be different from dest token")
      isValidate = false
    }
    if (isNaN(sourceAmount)) {
      sourceAmountError.push("Source amount is not number")
      isValidate = false
    }

    //verify min source amount
    if (this.props.tokens[this.props.limitOrder.sourceTokenSymbol].rate == 0) {
      sourceAmountError.push("This pair is under maintenance")
      isValidate = false
    }

    // var rateBig = converters.toTWei(this.props.tokens[this.props.limitOrder.sourceTokenSymbol].rate, 18)
    var ethEquivalentValue = this.calculateETHequivalent()

    if (ethEquivalentValue < constants.LIMIT_ORDER_CONFIG.minSupportOrder && !isNaN(sourceAmount)) {
      sourceAmountError.push(`Amount is too smalll. Limit order only support min ${constants.LIMIT_ORDER_CONFIG.minSupportOrder} ETH equivalent order`)
      isValidate = false
    }

    if (ethEquivalentValue > constants.LIMIT_ORDER_CONFIG.maxSupportOrder && !isNaN(sourceAmount)) {
      sourceAmountError.push(`Amount is too big. Limit order only support max ${constants.LIMIT_ORDER_CONFIG.maxSupportOrder} ETH equivalent order`)
      isValidate = false
    }

    // check rate is zero
    var triggerRate = parseFloat(this.props.limitOrder.triggerRate)
    if (isNaN(triggerRate)) {
      rateError.push("Trigger rate is not number")
      isValidate = false
    }
    // check rate is too big
    var triggerRateBig = converters.roundingRate(this.props.limitOrder.triggerRate)
    var percentChange = converters.percentChange(triggerRateBig, this.props.limitOrder.offeredRate)
    if (percentChange > constants.LIMIT_ORDER_CONFIG.maxPercentTriggerRate && !isNaN(triggerRate)) {
      rateError.push(`Trigger rate is too high, only allow ${constants.LIMIT_ORDER_CONFIG.maxPercentTriggerRate}% greater than the current rate`)
      isValidate = false
    }
    if (percentChange < constants.LIMIT_ORDER_CONFIG.minPercentTriggerRate && !isNaN(triggerRate)) {
      rateError.push(`Trigger rate is too low, please increase trigger rate. Minimum rate is 20% lower than current market rate.`)
      isValidate = false
    }

    //check balance
    var userBalance = this.getUserBalance()
    var srcAmount = this.getSourceAmount()
    if (converters.compareTwoNumber(userBalance, srcAmount) < 0) {
      sourceAmountError.push(`Your balance is insufficent for the order. Please check your ${this.props.limitOrder.sourceTokenSymbol} balance and your pending order`)
      isValidate = false
    }

    if (sourceAmountError.length > 0) {
      this.props.dispatch(limitOrderActions.throwError("sourceAmount", sourceAmountError))
    }
    if (rateError.length > 0) {
      this.props.dispatch(limitOrderActions.throwError("triggerRate", rateError))
    }

    if (!isValidate) {
      return
    }

    //check if he is agreed submit order
    console.log("isAgree")
    console.log(this.state.isAgree)
    if (this.state.isAgree) {
      this.findPathOrder()
      this.setState({ isAgree: false })
      return
    }

    // Filter active orders which have higher rate than current input rate
    const higherRateOrders = this.props.limitOrder.listOrder.filter(item => {
      return item.source === this.props.limitOrder.sourceTokenSymbol &&
            item.dest === this.props.limitOrder.destTokenSymbol &&
            item.address.toLowerCase() === this.props.account.address.toLowerCase() &&
            item.status === constants.LIMIT_ORDER_CONFIG.status.OPEN &&
            converters.compareTwoNumber(this.props.limitOrder.triggerRate, item.min_rate) < 1;
    });

    this.setState({
      higherRateOrders: higherRateOrders
    });

    if (higherRateOrders.length > 0) {
      if (!this.props.limitOrder.errors.rateWarning) {
        this.props.dispatch(limitOrderActions.throwError("rateWarning", "Lower rate"));
      }
    } else {
      if (this.props.limitOrder.errors.rateWarning) {
        this.props.dispatch(limitOrderActions.throwError("rateWarning", ""));
      }
      this.findPathOrder()
    }

  }

  getMaxGasApprove = () => {
    var tokens = this.props.tokens
    var sourceSymbol = this.props.limitOrder.sourceTokenSymbol
    if (tokens[sourceSymbol] && tokens[sourceSymbol].gasApprove) {
      return tokens[sourceSymbol].gasApprove
    } else {
      return this.props.limitOrder.max_gas_approve
    }
  }

  getMaxGasExchange = () => {
    const tokens = this.props.tokens
    var destTokenSymbol = BLOCKCHAIN_INFO.wrapETHToken
    var destTokenLimit = tokens[destTokenSymbol] && tokens[destTokenSymbol].gasLimit ? tokens[destTokenSymbol].gasLimit : this.props.limitOrder.max_gas

    return destTokenLimit;

  }

  getMaxGasLimit = (orderPath) => {
    var gasLimit = 0
    for (var i = 0; i < orderPath.length; i++) {
      switch (orderPath[i]) {
        case constants.LIMIT_ORDER_CONFIG.orderPath.approveZero:
        case constants.LIMIT_ORDER_CONFIG.orderPath.approveMax:
          gasLimit += this.getMaxGasApprove()
          break
        case constants.LIMIT_ORDER_CONFIG.orderPath.wrapETH:
          gasLimit += this.getMaxGasExchange()
          break
      }
    }
    return gasLimit
  }

  validateBalance = (orderPath) => {
    var gasLimit = this.getMaxGasLimit(orderPath)
    var totalFee = converters.calculateGasFee(this.props.limitOrder.gasPrice, gasLimit)
    var totalFeeBig = converters.toTWei(totalFee, 18)
    var ethBalance = this.props.tokens["ETH"].balance

    var compareValue
    if (this.props.limitOrder.sourceTokenSymbol === BLOCKCHAIN_INFO.wrapETHToken) {
      var srcAmount = this.getSourceAmount()
      var wrapETHTokenBalance = this.props.tokens[BLOCKCHAIN_INFO.wrapETHToken].balance
      compareValue = converters.sumOfTwoNumber(totalFeeBig, converters.subOfTwoNumber(srcAmount, wrapETHTokenBalance))
    } else {
      compareValue = totalFeeBig
    }
    return converters.compareTwoNumber(ethBalance, compareValue) < 0 ? false : true
  }

  async findPathOrder() {
    try {
      var orderPath = []
      // var currentPath = constants.LIMIT_ORDER_CONFIG.orderPath.confirmSubmitOrder
      var ethereum = this.props.ethereum
      // check wrapped eth
      var allowance = await ethereum.call("getAllowanceAtLatestBlock", this.props.limitOrder.sourceToken, this.props.account.address, BLOCKCHAIN_INFO.kyberswapAddress)
      if (allowance == 0) {
        orderPath = [constants.LIMIT_ORDER_CONFIG.orderPath.approveMax]
        // currentPath = constants.LIMIT_ORDER_CONFIG.orderPath.approveMax
      }
      if (allowance != 0 && allowance < Math.pow(10, 28)) {
        orderPath = [constants.LIMIT_ORDER_CONFIG.orderPath.approveZero, constants.LIMIT_ORDER_CONFIG.orderPath.approveMax]
        // currentPath = constants.LIMIT_ORDER_CONFIG.orderPath.approveZero
      }

      if (this.props.limitOrder.sourceTokenSymbol === BLOCKCHAIN_INFO.wrapETHToken) {
        var sourceToken = this.getSourceAmount()
        var userBalance = this.props.tokens[this.props.limitOrder.sourceTokenSymbol].balance
        if (converters.compareTwoNumber(userBalance, sourceToken) < 0) {
          orderPath.push(constants.LIMIT_ORDER_CONFIG.orderPath.wrapETH)
        }
      }
      orderPath.push(constants.LIMIT_ORDER_CONFIG.orderPath.confirmSubmitOrder)
      orderPath.push(constants.LIMIT_ORDER_CONFIG.orderPath.submitStatusOrder)

      //check balance eth is enough

      if (this.validateBalance(orderPath)) {
        this.props.dispatch(limitOrderActions.updateOrderPath(orderPath, 0))
      } else {
        console.log("Your eth balance is not enough for transactions")
        var title = this.props.translate("error.error_occurred") || "Error occurred"
        var content = "Your eth balance is not enough for transactions"
        this.props.dispatch(utilActions.openInfoModal(title, content))
      }

    } catch (err) {
      console.log(err)

      var title = this.props.translate("error.error_occurred") || "Error occurred"
      var content = this.props.translate("error.node_error") || "There are some problems with nodes. Please try again in a while."
      this.props.dispatch(utilActions.openInfoModal(title, content))

    }
  }

  submitOrder = () => {
    if (!isUserLogin()) {
      window.location.href = "/users/sign_in"
    }

    if (this.props.account !== false) {
      this.validateOrder()
    }
  }

  agreeSubmit = () => {
    this.setState({ isAgree: true }, () => {
      this.submitOrder()
    })
  }

  getRateWarningTooltip = () => {
    if (!this.props.account) {
      return null;
    }

    const tableComp = this.state.higherRateOrders.map(item => {
      const datetime = common.getFormattedDate(item.status === constants.LIMIT_ORDER_CONFIG.status.OPEN || constants.LIMIT_ORDER_CONFIG.status.IN_PROGRESS ? item.created_time : item.cancel_time);
      const rate = converters.roundingNumber(item.min_rate);
      return (

{datetime}

{`${item.source.toUpperCase()}/${item.dest.toUpperCase()} >= ${rate}`}

      );
    });

    return (

        {/* Description */}

          {this.props.translate("limit_order.lower_rate_warning") || "This new order has a lower rate than some orders you have created. Below orders will be cancelled when you submitted this order.s"}

        {/* Table */}

          {tableComp}

        {/* Buttons */}

          <button
						className="btn-cancel"
						onClick={e => this.closeRateWarningTooltip()}
					>
						{this.props.translate("limit_order.change_rate") || "Change Rate"}
					</button>
					<button
						className="btn-confirm"
						onClick={e => this.confirmAgreeSubmit()}
					>
						{this.props.translate("import.yes") || "Yes"}
					</button>

    );
  }

  closeRateWarningTooltip = () => {
    this.props.dispatch(limitOrderActions.throwError("rateWarning", ""));
  }

  confirmAgreeSubmit = () => {
    this.props.dispatch(limitOrderActions.throwError("rateWarning", ""));
    this.props.dispatch(limitOrderActions.setPendingCancelOrders(this.state.higherRateOrders));
    this.agreeSubmit();
  }

  render() {
    var isDisable = isUserLogin() && this.props.account == false
    var isWaiting = this.props.limitOrder.isSelectToken || this.props.limitOrder.errors.sourceAmount.length > 0 || this.props.limitOrder.errors.triggerRate.length > 0
    return (

        {(
          <Tooltip
            open={this.props.limitOrder.errors.rateWarning !== ""}
            position="right"
            interactive={true}
            animateFill={false}
            onRequestClose={() => this.closeRateWarningTooltip()}
            html={this.getRateWarningTooltip()}
          >
            <button className={`accept-button ${isDisable ? "disable" : ""} ${isWaiting ? "waiting" : ""}`} onClick={this.submitOrder}>
              {isUserLogin() ? "Submit" : "Login to Submit Order"}
            </button>
          </Tooltip>
        )}

          {this.props.limitOrder.orderPath[this.props.limitOrder.currentPathIndex] === constants.LIMIT_ORDER_CONFIG.orderPath.approveZero && <ApproveZeroModal getMaxGasApprove={this.getMaxGasApprove.bind(this)} />}
          {this.props.limitOrder.orderPath[this.props.limitOrder.currentPathIndex] === constants.LIMIT_ORDER_CONFIG.orderPath.approveMax && <ApproveMaxModal getMaxGasApprove={this.getMaxGasApprove.bind(this)} />}
          {this.props.limitOrder.orderPath[this.props.limitOrder.currentPathIndex] === constants.LIMIT_ORDER_CONFIG.orderPath.wrapETH && <WrapETHModal getOpenOrderAmount={this.props.getOpenOrderAmount} />}
          {this.props.limitOrder.orderPath[this.props.limitOrder.currentPathIndex] === constants.LIMIT_ORDER_CONFIG.orderPath.confirmSubmitOrder && <ConfirmModal />}
          {this.props.limitOrder.orderPath[this.props.limitOrder.currentPathIndex] === constants.LIMIT_ORDER_CONFIG.orderPath.submitStatusOrder && <SubmitStatusModal />}

    )
  }
}
<|endoftext|>


<commit_before>// Game implementation

// Target object is the goald object
/*
in:
    targetValue  = int

Description

*/
var Target = function(targetValue) {
    this.targetValue = targetValue;
    this.currentValue = 0;
    this.posx = 250;
    this.posy = 250;
    this.radius = 100;
    this.includedParts = [];
    this.partIds =[];
    var color = getRandomColor();

    this.includePart = function(part, sign) {
        if (sign === "add") {
            this.currentValue += Math.abs(part.value);
        } else if(sign === "sub") {
            this.currentValue -= Math.abs(part.value);
        } else if(sign === "multi") {
            this.currentValue *= Math.abs(part.value);
        }
        this.includedParts.push([part, sign]);
        this.partIds.push(part.id);
    };

    this.excludePart = function(part) {
        var index = this.partIds.indexOf(part.id);
        sign = this.includedParts[index][1]
        if (sign === "add") {
            this.currentValue -= Math.abs(part.value);
        } else if(sign === "sub") {
            this.currentValue += Math.abs(part.value);
        } else if(sign === "multi") {
            this.currentValue /= Math.abs(part.value);
        }
        if (index > -1) {
            this.partIds.splice(index, 1);
            this.includedParts.splice(index, 1);

        };
    };

    this.draw = function() {
        context.beginPath();

        context.arc(this.posx, this.posy, this.radius,0,2*Math.PI);
        context.fillStyle = color;
        context.fill();
        context.stroke();

        context.fillStyle = '#000000';
        context.font="40px Georgia";
        context.fillText(this.targetValue, this.posx - 30, this.posy - 10);
        context.font="40px Georgia";
        context.fillText(this.currentValue, this.posx - 30, this.posy + 20);
    };
};

var idCounter = 0;

var Part = function(value) {
    this.value = value;
    this.posx = 50+ Math.random()*300;
    this.posy = 100;
    this.radius = 30;
    this.id = idCounter++;
    var color = getRandomColor();

    this.draw = function() {

        context.beginPath();
        context.arc(this.posx, this.posy, this.radius,0,2*Math.PI);
        context.fillStyle = color;
        context.fill();
        context.stroke();
        context.fillStyle = '#000000';
        context.font="20px Georgia";
        context.fillText(Math.abs(this.value), this.posx - 20, this.posy + 10);
    };
};

function getRandomColor() {
    var letters = '0123456789ABCDEF'.split('');
    var color = '#';
    for (var i = 0; i < 6; i++ ) {
        color += letters[Math.floor(Math.random() * 16)];
    }
    return color;
}

var GameSession = function() {

    var theCanvas   = document.getElementById("myCanvas");
    var bRect       = theCanvas.getBoundingClientRect();
    var number_of_parts;
    var parts;
    var target;
    var context     = theCanvas.getContext("2d");
    var dragging;
    var dragIndex;
    var dragHoldX;
    var dragHoldY;
    var level       = 0;
    var add         = false;
    var sub         = false;
    var multi       = false;

    this.addition = function () {
        if(add === false){
             sub    = false;
             multi  = false;
             add    = true;
             document.getElementById("additionButton").style.background="rgba(255,0,0,0.6)"
             document.getElementById("subtractionButton").style.background="#D3D3D3"
             document.getElementById("multiplicationButton").style.background="#D3D3D3"
        } else if (add === true){
             add    = false;
             document.getElementById("additionButton").style.background="#D3D3D3"
        }
    }
    this.subtraction = function () {
        if(sub === false){
             add    = false;
             multi  = false;
             sub    = true;
             document.getElementById("subtractionButton").style.background="rgba(255,0,0,0.6)"
             document.getElementById("additionButton").style.background="#D3D3D3"
             document.getElementById("multiplicationButton").style.background="#D3D3D3"
        } else if (sub === true){
             sub    = false;
             document.getElementById("subtractionButton").style.background="#D3D3D3"

        }
    }
    this.multiplication = function () {
        if(multi === false){
             add    = false;
             sub    = false;
             multi  = true
             document.getElementById("multiplicationButton").style.background="rgba(255,0,0,0.6)"
             document.getElementById("subtractionButton").style.background="#D3D3D3"
             document.getElementById("additionButton").style.background="#D3D3D3"
        } else if (multi === true){
             multi  = false;
             document.getElementById("multiplicationButton").style.background="#D3D3D3"
        }
    }
    this.get_sign = function () {
        if (add === true) {
           return "add"
        } else if (sub === true) {
           return "sub"
        } else if (multi === true) {
           return "multi"
        }
    }

    function init() {

        //later in make target function
        //target = new Target(10);
        parts = [];
        level++;
        var sumParts = 0;
        for (var i=0;i<level-1;i++) {
            var partValue = Math.floor((Math.random() * 10) + 1)
            sign = Math.floor(Math.random() * 3)
            if (sign === 1) {
                console.log("hello")
                sumParts += partValue
            } else if (sign === 2){
                sumParts -= partValue
            } else if (sign === 3) {
                sumParts *= partValue
            }
            parts[i]     = new Part(partValue);
        };

        //parts[level-1] = new Part(offSet - sumParts);
        target = new Target(sumParts)
        target.draw();

    };
    function drawParts() {
        for (i = 0; i < parts.length; i++) {
            parts[i].draw();
        };
    };
    function drawAll() {
    }
    //function newGame() {
    this.newGame = function() {
        this.reset();
        init();
        drawAll();
        target.draw();
        drawParts();

        theCanvas.addEventListener("mousedown", mouseDownListener, false);
    };
    function mouseDownListener(evt) {
        var i;
        //We are going to pay attention to the layering order of the objects so that if a mouse down occurs over more than object,
        //only the topmost one will be dragged.
        var highestIndex = -1;
        dragIndex = -1;

        mouseX = (evt.clientX - bRect.left)*(theCanvas.width/bRect.width);
        mouseY = (evt.clientY - bRect.top)*(theCanvas.height/bRect.height);
        dragging = false;
        for (i=0; i < parts.length; i++) {
            if  (hitTest(parts[i], mouseX, mouseY)) {
                dragging = true;
                if (i > highestIndex) {
                    //We will pay attention to the point on the object where the mouse is "holding" the object:
                    dragHoldX = mouseX - parts[i].posx;
                    dragHoldY = mouseY - parts[i].posy;
                    highestIndex = i;
                    dragIndex = i;
                }
            }
        }

        if (dragging) {
            theCanvas.addEventListener("mousemove", mouseMoveListener, false);
        }

        theCanvas.removeEventListener("mousedown", mouseDownListener, false);
        window.addEventListener("mouseup", mouseUpListener, false);
    }

    function mouseUpListener(evt) {
    //this.mouseUpListener = function(evt) {
        mouseX = (evt.clientX - bRect.left)*(theCanvas.width/bRect.width);
        mouseY = (evt.clientY - bRect.top)*(theCanvas.height/bRect.height);

        theCanvas.removeEventListener("mouseup", mouseUpListener, false);
        theCanvas.addEventListener("mousedown", mouseDownListener, false);
        if (dragIndex >= 0){
            if (hitTest(target, mouseX, mouseY) && (target.partIds.indexOf(parts[dragIndex].id) < 0)) {
                target.includePart(parts[dragIndex], gamesession.get_sign());
                context.clearRect(0, 0, b.width, b.height);
                context.restore();
                target.draw();
                drawParts();

            } else if ( dragging === true && hitTest(target, mouseX,mouseY) === false && (target.partIds.indexOf(parts[dragIndex].id) > -1)) {
                target.excludePart(parts[dragIndex]);
                context.clearRect(0, 0, b.width, b.height);
                context.restore();
                target.draw();
                drawParts();
            };
        };

        if (dragging) {
            dragging = false;
            theCanvas.removeEventListener("mousemove", mouseMoveListener, false);
        }
        console.log("3")

        completed();
    };

    function hitTest(shape,mx,my) {

        var dx;
        var dy;
        dx = mx - shape.posx;
        dy = my - shape.posy;

        //a "hit" will be registered if the distance away from the center is less than the radius of the circular object
        return (dx*dx + dy*dy < shape.radius*shape.radius);
    };

    //this.mouseMoveListener = function(evt) {
    function mouseMoveListener(evt) {
        var posX;
        var posY;
        var partRad = parts[dragIndex].radius;
        var minX = partRad;
        var maxX = theCanvas.width - partRad;
        var minY = partRad;
        var maxY = theCanvas.height - partRad;
        //getting mouse position correctly
        var bRect = theCanvas.getBoundingClientRect();
        mouseX = (evt.clientX - bRect.left)*(theCanvas.width/bRect.width);
        mouseY = (evt.clientY - bRect.top)*(theCanvas.height/bRect.height);

        //clamp x and y positions to prevent object from dragging outside of canvas
        posX = mouseX - dragHoldX;
        posX = (posX < minX) ? minX : ((posX > maxX) ? maxX : posX);
        posY = mouseY - dragHoldY;
        posY = (posY < minY) ? minY : ((posY > maxY) ? maxY : posY);

        parts[dragIndex].posx = posX;
        parts[dragIndex].posy = posY;
        context.clearRect(0, 0, b.width, b.height);
        context.restore();
        target.draw();
        drawParts();

    };

    this.update = function() {
        // Draw all parts

        // Check if click is inside target and recalculate

        // Check if target is completed

    };

    this.reset = function() {
        idCounter = 0;
        context.clearRect(0, 0, b.width, b.height);
        context.restore();
        this.parts = [];
    };
    function completed() {
        if (target.includedParts.length === parts.length
            && target.currentValue === target.targetValue) {
            alert("You Got it")
            gamesession.newGame();
        };

    };
};

var b = document.getElementById("myCanvas");
var context = b.getContext("2d");

gamesession = new GameSession()

//document.getElementById("body").onload = function() {gamesession.init()};

<commit_msg>level start att 1 part and round to get sign<commit_after>// Game implementation

// Target object is the goald object
/*
in:
    targetValue  = int

Description

*/
var Target = function(targetValue) {
    this.targetValue = targetValue;
    this.currentValue = 0;
    this.posx = 250;
    this.posy = 250;
    this.radius = 100;
    this.includedParts = [];
    this.partIds =[];
    var color = getRandomColor();

    this.includePart = function(part, sign) {
        if (sign === "add") {
            this.currentValue += Math.abs(part.value);
        } else if(sign === "sub") {
            this.currentValue -= Math.abs(part.value);
        } else if(sign === "multi") {
            this.currentValue *= Math.abs(part.value);
        }
        this.includedParts.push([part, sign]);
        this.partIds.push(part.id);
    };

    this.excludePart = function(part) {
        var index = this.partIds.indexOf(part.id);
        sign = this.includedParts[index][1]
        if (sign === "add") {
            this.currentValue -= Math.abs(part.value);
        } else if(sign === "sub") {
            this.currentValue += Math.abs(part.value);
        } else if(sign === "multi") {
            this.currentValue /= Math.abs(part.value);
        }
        if (index > -1) {
            this.partIds.splice(index, 1);
            this.includedParts.splice(index, 1);

        };
    };

    this.draw = function() {
        context.beginPath();

        context.arc(this.posx, this.posy, this.radius,0,2*Math.PI);
        context.fillStyle = color;
        context.fill();
        context.stroke();

        context.fillStyle = '#000000';
        context.font="40px Georgia";
        context.fillText(this.targetValue, this.posx - 30, this.posy - 10);
        context.font="40px Georgia";
        context.fillText(this.currentValue, this.posx - 30, this.posy + 20);
    };
};

var idCounter = 0;

var Part = function(value) {
    this.value = value;
    this.posx = 50+ Math.random()*300;
    this.posy = 100;
    this.radius = 30;
    this.id = idCounter++;
    var color = getRandomColor();

    this.draw = function() {

        context.beginPath();
        context.arc(this.posx, this.posy, this.radius,0,2*Math.PI);
        context.fillStyle = color;
        context.fill();
        context.stroke();
        context.fillStyle = '#000000';
        context.font="20px Georgia";
        context.fillText(Math.abs(this.value), this.posx - 20, this.posy + 10);
    };
};

function getRandomColor() {
    var letters = '0123456789ABCDEF'.split('');
    var color = '#';
    for (var i = 0; i < 6; i++ ) {
        color += letters[Math.floor(Math.random() * 16)];
    }
    return color;
}

var GameSession = function() {

    var theCanvas   = document.getElementById("myCanvas");
    var bRect       = theCanvas.getBoundingClientRect();
    var number_of_parts;
    var parts;
    var target;
    var context     = theCanvas.getContext("2d");
    var dragging;
    var dragIndex;
    var dragHoldX;
    var dragHoldY;
    var level       = 0;
    var add         = false;
    var sub         = false;
    var multi       = false;

    this.addition = function () {
        if(add === false){
             sub    = false;
             multi  = false;
             add    = true;
             document.getElementById("additionButton").style.background="rgba(255,0,0,0.6)"
             document.getElementById("subtractionButton").style.background="#D3D3D3"
             document.getElementById("multiplicationButton").style.background="#D3D3D3"
        } else if (add === true){
             add    = false;
             document.getElementById("additionButton").style.background="#D3D3D3"
        }
    }
    this.subtraction = function () {
        if(sub === false){
             add    = false;
             multi  = false;
             sub    = true;
             document.getElementById("subtractionButton").style.background="rgba(255,0,0,0.6)"
             document.getElementById("additionButton").style.background="#D3D3D3"
             document.getElementById("multiplicationButton").style.background="#D3D3D3"
        } else if (sub === true){
             sub    = false;
             document.getElementById("subtractionButton").style.background="#D3D3D3"

        }
    }
    this.multiplication = function () {
        if(multi === false){
             add    = false;
             sub    = false;
             multi  = true
             document.getElementById("multiplicationButton").style.background="rgba(255,0,0,0.6)"
             document.getElementById("subtractionButton").style.background="#D3D3D3"
             document.getElementById("additionButton").style.background="#D3D3D3"
        } else if (multi === true){
             multi  = false;
             document.getElementById("multiplicationButton").style.background="#D3D3D3"
        }
    }
    this.get_sign = function () {
        if (add === true) {
           return "add"
        } else if (sub === true) {
           return "sub"
        } else if (multi === true) {
           return "multi"
        }
    }

    function init() {

        //later in make target function
        //target = new Target(10);
        parts = [];
        level++;
        var sumParts = 0;
        for (var i=0;i<level;i++) {
            var partValue = Math.floor((Math.random() * 10) + 1)
            sign = Math.round(Math.random() * 2)
            if (sign === 0) {
                console.log("hello")
                sumParts += partValue
            } else if (sign === 1){
                sumParts -= partValue
            } else if (sign === 2) {
                sumParts *= partValue
            }
            parts[i]     = new Part(partValue);
        };

        //parts[level-1] = new Part(offSet - sumParts);
        target = new Target(sumParts)
        target.draw();

    };
    function drawParts() {
        for (i = 0; i < parts.length; i++) {
            parts[i].draw();
        };
    };
    function drawAll() {
    }
    //function newGame() {
    this.newGame = function() {
        this.reset();
        init();
        drawAll();
        target.draw();
        drawParts();

        theCanvas.addEventListener("mousedown", mouseDownListener, false);
    };
    function mouseDownListener(evt) {
        var i;
        //We are going to pay attention to the layering order of the objects so that if a mouse down occurs over more than object,
        //only the topmost one will be dragged.
        var highestIndex = -1;
        dragIndex = -1;

        mouseX = (evt.clientX - bRect.left)*(theCanvas.width/bRect.width);
        mouseY = (evt.clientY - bRect.top)*(theCanvas.height/bRect.height);
        dragging = false;
        for (i=0; i < parts.length; i++) {
            if  (hitTest(parts[i], mouseX, mouseY)) {
                dragging = true;
                if (i > highestIndex) {
                    //We will pay attention to the point on the object where the mouse is "holding" the object:
                    dragHoldX = mouseX - parts[i].posx;
                    dragHoldY = mouseY - parts[i].posy;
                    highestIndex = i;
                    dragIndex = i;
                }
            }
        }

        if (dragging) {
            theCanvas.addEventListener("mousemove", mouseMoveListener, false);
        }

        theCanvas.removeEventListener("mousedown", mouseDownListener, false);
        window.addEventListener("mouseup", mouseUpListener, false);
    }

    function mouseUpListener(evt) {
    //this.mouseUpListener = function(evt) {
        mouseX = (evt.clientX - bRect.left)*(theCanvas.width/bRect.width);
        mouseY = (evt.clientY - bRect.top)*(theCanvas.height/bRect.height);

        theCanvas.removeEventListener("mouseup", mouseUpListener, false);
        theCanvas.addEventListener("mousedown", mouseDownListener, false);
        if (dragIndex >= 0){
            if (hitTest(target, mouseX, mouseY) && (target.partIds.indexOf(parts[dragIndex].id) < 0)) {
                target.includePart(parts[dragIndex], gamesession.get_sign());
                context.clearRect(0, 0, b.width, b.height);
                context.restore();
                target.draw();
                drawParts();

            } else if ( dragging === true && hitTest(target, mouseX,mouseY) === false && (target.partIds.indexOf(parts[dragIndex].id) > -1)) {
                target.excludePart(parts[dragIndex]);
                context.clearRect(0, 0, b.width, b.height);
                context.restore();
                target.draw();
                drawParts();
            };
        };

        if (dragging) {
            dragging = false;
            theCanvas.removeEventListener("mousemove", mouseMoveListener, false);
        }
        console.log("3")

        completed();
    };

    function hitTest(shape,mx,my) {

        var dx;
        var dy;
        dx = mx - shape.posx;
        dy = my - shape.posy;

        //a "hit" will be registered if the distance away from the center is less than the radius of the circular object
        return (dx*dx + dy*dy < shape.radius*shape.radius);
    };

    //this.mouseMoveListener = function(evt) {
    function mouseMoveListener(evt) {
        var posX;
        var posY;
        var partRad = parts[dragIndex].radius;
        var minX = partRad;
        var maxX = theCanvas.width - partRad;
        var minY = partRad;
        var maxY = theCanvas.height - partRad;
        //getting mouse position correctly
        var bRect = theCanvas.getBoundingClientRect();
        mouseX = (evt.clientX - bRect.left)*(theCanvas.width/bRect.width);
        mouseY = (evt.clientY - bRect.top)*(theCanvas.height/bRect.height);

        //clamp x and y positions to prevent object from dragging outside of canvas
        posX = mouseX - dragHoldX;
        posX = (posX < minX) ? minX : ((posX > maxX) ? maxX : posX);
        posY = mouseY - dragHoldY;
        posY = (posY < minY) ? minY : ((posY > maxY) ? maxY : posY);

        parts[dragIndex].posx = posX;
        parts[dragIndex].posy = posY;
        context.clearRect(0, 0, b.width, b.height);
        context.restore();
        target.draw();
        drawParts();

    };

    this.update = function() {
        // Draw all parts

        // Check if click is inside target and recalculate

        // Check if target is completed

    };

    this.reset = function() {
        idCounter = 0;
        context.clearRect(0, 0, b.width, b.height);
        context.restore();
        this.parts = [];
    };
    function completed() {
        if (target.includedParts.length === parts.length
            && target.currentValue === target.targetValue) {
            alert("You Got it")
            gamesession.newGame();
        };

    };
};

var b = document.getElementById("myCanvas");
var context = b.getContext("2d");

gamesession = new GameSession()

//document.getElementById("body").onload = function() {gamesession.init()};

<|endoftext|>