<commit_before>/* Captures Window messages.
 *
 * Copyright 2017 Raising the Floor - International
 *
 * Licensed under the New BSD license. You may not use this file except in
 * compliance with this License.
 *
 * The R&D leading to these results received funding from the
 * Department of Education - Grant H421A150005 (GPII-APCP). However,
 * these results do not necessarily represent the policy of the
 * Department of Education, and you should not assume endorsement by the
 * Federal Government.
 *
 * You may obtain a copy of the License at
 * https://github.com/GPII/universal/blob/master/LICENSE.txt
 */

"use strict";

var fluid = require("gpii-universal"),
    ffi = require("ffi-napi");

var gpii = fluid.registerNamespace("gpii");
fluid.registerNamespace("gpii.windows.messages");

require("../../WindowsUtilities/WindowsUtilities.js");

var windows = gpii.windows;

fluid.defaults("gpii.windows.messages", {
    gradeNames: ["fluid.component", "fluid.resolveRootSingle"],
    singleRootType: "gpii.windows.messages",
    events: {
        /**
         * Message received (see https://msdn.microsoft.com/library/ms633573)
         * Set `result.value` to specify a return value (and not call DefWindowProc).
         *
         * @param {Number} hwnd Window handle
         * @param {Number} msg Message (WM_*)
         * @param {Number} wParam Message specific data
         * @param {Buffer} lParam  Message specific data (ffi pointer, may not be valid after return)
         * @param {object} result Set a 'value' field to specify a return value.
         */
        "onMessage": null
    },
    listeners: {
        "onDestroy": {
            funcName: "gpii.windows.messages.stop",
            args: "{that}"
        }
    },
    invokers: {
        start: {
            funcName: "gpii.windows.messages.start",
            args: [
                "{that}",
                "{arguments}.0"
            ]
        },
        stop: {
            funcName: "gpii.windows.messages.stop",
            args: [
                "{that}",
                "{arguments}.0"
            ]
        },
        getWindowHandle: {
            funcName: "gpii.windows.messages.getWindowHandle",
            args: "{that}"
        }
    },
    members: {
        /** Message window class */
        windowClassname: "gpii-message-window",
        /** Window handle of the message window (multiple component instances use the same window). */
        messageWindow: null,
        /** A reference counter, so the window can be destroyed when no longer required. */
        references: []
    }
});

/**
 * Start listening for window messages. Create the message window if it's not already created.
 *
 * @param {Component} that The gpii.windows.messages instance.
 * @param {Component} component The component that's listening for messages.
 */
gpii.windows.messages.start = function (that, component) {
    var id = component.id;
    if (that.references.indexOf(id) === -1) {
        if (!that.messageWindow) {
            gpii.windows.messages.createMessageWindow(that);
        }
        that.references.push(id);
    }
};

/**
 * Stop listening for window messages. Destroy the message window if no other instance is using it.
 *
 * @param {Component} that The gpii.windows.messages instance.
 * @param {Component} component The component that's listening for messages, or null to stop completely.
 */
gpii.windows.messages.stop = function (that, component) {
    if (component) {
        var id = component.id;
        var index = that.references.indexOf(id);
        if (index > -1) {
            that.references.splice(index, 1);
        }
    } else {
        that.references = [];
    }

    if (that.references.length === 0) {
        gpii.windows.messages.destroyMessageWindow(that);
    }
};

/**
 * Gets the handle of the message window (hWnd).
 *
 * @param {Component} that The gpii.windows.messages instance.
 * @return {Number} The handle, or 0 if there's no window.
 */
gpii.windows.messages.getWindowHandle = function (that) {
    return that.messageWindow || 0;
};

/**
 * The window procedure for the window. Fires the event, then passes the message to the default window procedure.
 *
 * WindowProc: https://msdn.microsoft.com/library/ms633573
 *
 * @param {Component} that The gpii.windows.messages instance.
 * @param {Number} hwnd Window handle.
 * @param {Number} msg The window message.
 * @param {Number} wParam Message parameter.
 * @param {Number} lParam Message parameter.
 * @return {Number} The result of the message.
 */
gpii.windows.messages.windowProc = function (that, hwnd, msg, wParam, lParam) {
    var result = { value: undefined };
    that.events.onMessage.fire(hwnd, msg, wParam, lParam, result);

    if (result.value === undefined) {
        return windows.user32.DefWindowProcW(hwnd, msg, wParam, lParam);
    } else {
        return result.value;
    }
};

/**
 * Polls for messages - only required if not running under electron.
 *
 * @param {Component} that The gpii.windows.messages instance.
 */
gpii.windows.messages.messagePump = function (that) {

    var loop = function () {
        // sizeof(MSG) = 48 on 64-bit, 28 on 32-bit.
        var msg = Buffer.alloc(process.arch === "x64" ? 48 : 28);

        // Unable to use GetMessage because it blocks, and can't call via .async because it needs to be in the same
        // thread as the window.
        while (gpii.windows.user32.PeekMessageW(msg, 0, 0, 0, 1)) {
            gpii.windows.user32.TranslateMessage(msg);
            gpii.windows.user32.DispatchMessageW(msg);
        }

        if (that.messageWindow) {
            setTimeout(loop, gpii.windows.messages.messagePumpDelay);
        }
    };
    loop();
};

/** Delay in ms for polling messages. Only applies when running as gpii-windows. */
gpii.windows.messages.messagePumpDelay = 2000;

/**
 * Creates a window whose only purpose is to receive messages.
 *
 * The window has no visibility - it just exists.
 *
 * @param {Component} that The gpii.windows.messages instance.
 */
gpii.windows.messages.createMessageWindow = function (that) {
    if (that.messageWindow) {
        fluid.fail("Message window already created.");
    }

    var className = windows.stringToWideChar(that.windowClassname);

    // Window class only needs to be registered once.
    if (!gpii.windows.messages.windowProcPointer) {
        // Create the Window Class for the window
        var cls = new windows.WNDCLASSW();
        cls.ref().fill(0);

        cls.lpszClassName = className;

        // Create a pointer to the window procedure function.
        var callback = ffi.Callback(gpii.windows.types.HANDLE,
            [gpii.windows.types.HANDLE, gpii.windows.types.UINT, gpii.windows.types.UINT, gpii.windows.types.PVOID],
            function (hwnd, msg, wParam, lParam) {
                return gpii.windows.messages.windowProc(that, hwnd, msg, wParam, lParam);
            });
        cls.lpfnWndProc = callback;

        // Keep a reference to the function pointer, otherwise the GC will free it. Because 'callback' points to the
        // function that windows calls whenever there is a window message, the buffers need to be kept alive for as long
        // as GPII is running.
        // Additionally, this value also contains the "cif" buffer (internal to ffi-napi) which also needs to be
        // referenced. See GPII-3445.
        gpii.windows.messages.windowProcPointer = function () {
            callback();
        };

        var result = gpii.windows.user32.RegisterClassW(cls.ref());
        if (!result) {
            fluid.fail("RegisterClass failed");
        }
    }

    // Start the message loop
    if (!process.versions.electron) {
        that.messageWindow = 1; // make the loop think there's already a window.
        gpii.windows.messages.messagePump(that);
    }

    // Create the Window.
    that.messageWindow =
        gpii.windows.user32.CreateWindowExW(0, className, className, 0, 0, 0, 0, 0, 0, 0, 0, 0);

    if (!that.messageWindow) {
        fluid.fail("CreateWindowEx failed");
    }
};

/**
 * Destroys the window created by createMessageWindow.
 *
 * @param {Component} that The gpii.windows.messages instance.
 * @return {Boolean} false on failure.
 */
gpii.windows.messages.destroyMessageWindow = function (that) {
    var success = true;
    if (that.messageWindow) {
        // Destroy the window.
        success = !!windows.user32.DestroyWindow(that.messageWindow);
        that.messageWindow = null;
        that.references = [];
    }
    return success;
};

/**
 * Send a message to a window, returning a promise which resolve when the message has been processed.
 *
 * @param {Number} hwnd The window handle.
 * @param {Number} msg The message, or the name of a registered message (will be registered if not already).
 * @param {Number} wParam Message parameter.
 * @param {Number} lParam Message parameter.
 * @param {Number} timeout [optional] Number of milliseconds to wait before resolving with "timeout".
 * @return {Promise} Resolves when the message has been processed.
 */
gpii.windows.messages.sendMessageAsync = function (hwnd, msg, wParam, lParam, timeout) {
    var promise = fluid.promise();
    var complete = false;

    var timer = timeout && setTimeout(function () {
        if (!complete) {
            complete = true;
            promise.resolve("timeout");
        }
    }, timeout);

    var t = gpii.windows.types;
    var callback = ffi.Callback(t.BOOL, [t.HANDLE, t.UINT, t.UINT, t.ULONG_PTR, t.ULONG_PTR],
        function (w, m, wp, lp, result) {
            if (!complete) {
                complete = true;
                promise.resolve(result);
            }
            if (timer) {
                clearTimeout(timer);
            }
        });

    gpii.windows.user32.SendMessageCallbackW(hwnd, msg, wParam, lParam, callback, 0);

    // Keep the callback alive for as long as the promise.
    promise.then(function () {
        return callback && null;
    });

    return promise;
};

gpii.windows.messages();
<commit_msg>GPII-3558: Clarified strange looking code.<commit_after>/* Captures Window messages.
 *
 * Copyright 2017 Raising the Floor - International
 *
 * Licensed under the New BSD license. You may not use this file except in
 * compliance with this License.
 *
 * The R&D leading to these results received funding from the
 * Department of Education - Grant H421A150005 (GPII-APCP). However,
 * these results do not necessarily represent the policy of the
 * Department of Education, and you should not assume endorsement by the
 * Federal Government.
 *
 * You may obtain a copy of the License at
 * https://github.com/GPII/universal/blob/master/LICENSE.txt
 */

"use strict";

var fluid = require("gpii-universal"),
    ffi = require("ffi-napi");

var gpii = fluid.registerNamespace("gpii");
fluid.registerNamespace("gpii.windows.messages");

require("../../WindowsUtilities/WindowsUtilities.js");

var windows = gpii.windows;

fluid.defaults("gpii.windows.messages", {
    gradeNames: ["fluid.component", "fluid.resolveRootSingle"],
    singleRootType: "gpii.windows.messages",
    events: {
        /**
         * Message received (see https://msdn.microsoft.com/library/ms633573)
         * Set `result.value` to specify a return value (and not call DefWindowProc).
         *
         * @param {Number} hwnd Window handle
         * @param {Number} msg Message (WM_*)
         * @param {Number} wParam Message specific data
         * @param {Buffer} lParam  Message specific data (ffi pointer, may not be valid after return)
         * @param {object} result Set a 'value' field to specify a return value.
         */
        "onMessage": null
    },
    listeners: {
        "onDestroy": {
            funcName: "gpii.windows.messages.stop",
            args: "{that}"
        }
    },
    invokers: {
        start: {
            funcName: "gpii.windows.messages.start",
            args: [
                "{that}",
                "{arguments}.0"
            ]
        },
        stop: {
            funcName: "gpii.windows.messages.stop",
            args: [
                "{that}",
                "{arguments}.0"
            ]
        },
        getWindowHandle: {
            funcName: "gpii.windows.messages.getWindowHandle",
            args: "{that}"
        }
    },
    members: {
        /** Message window class */
        windowClassname: "gpii-message-window",
        /** Window handle of the message window (multiple component instances use the same window). */
        messageWindow: null,
        /** A reference counter, so the window can be destroyed when no longer required. */
        references: []
    }
});

/**
 * Start listening for window messages. Create the message window if it's not already created.
 *
 * @param {Component} that The gpii.windows.messages instance.
 * @param {Component} component The component that's listening for messages.
 */
gpii.windows.messages.start = function (that, component) {
    var id = component.id;
    if (that.references.indexOf(id) === -1) {
        if (!that.messageWindow) {
            gpii.windows.messages.createMessageWindow(that);
        }
        that.references.push(id);
    }
};

/**
 * Stop listening for window messages. Destroy the message window if no other instance is using it.
 *
 * @param {Component} that The gpii.windows.messages instance.
 * @param {Component} component The component that's listening for messages, or null to stop completely.
 */
gpii.windows.messages.stop = function (that, component) {
    if (component) {
        var id = component.id;
        var index = that.references.indexOf(id);
        if (index > -1) {
            that.references.splice(index, 1);
        }
    } else {
        that.references = [];
    }

    if (that.references.length === 0) {
        gpii.windows.messages.destroyMessageWindow(that);
    }
};

/**
 * Gets the handle of the message window (hWnd).
 *
 * @param {Component} that The gpii.windows.messages instance.
 * @return {Number} The handle, or 0 if there's no window.
 */
gpii.windows.messages.getWindowHandle = function (that) {
    return that.messageWindow || 0;
};

/**
 * The window procedure for the window. Fires the event, then passes the message to the default window procedure.
 *
 * WindowProc: https://msdn.microsoft.com/library/ms633573
 *
 * @param {Component} that The gpii.windows.messages instance.
 * @param {Number} hwnd Window handle.
 * @param {Number} msg The window message.
 * @param {Number} wParam Message parameter.
 * @param {Number} lParam Message parameter.
 * @return {Number} The result of the message.
 */
gpii.windows.messages.windowProc = function (that, hwnd, msg, wParam, lParam) {
    var result = { value: undefined };
    that.events.onMessage.fire(hwnd, msg, wParam, lParam, result);

    if (result.value === undefined) {
        return windows.user32.DefWindowProcW(hwnd, msg, wParam, lParam);
    } else {
        return result.value;
    }
};

/**
 * Polls for messages - only required if not running under electron.
 *
 * @param {Component} that The gpii.windows.messages instance.
 */
gpii.windows.messages.messagePump = function (that) {

    var loop = function () {
        // sizeof(MSG) = 48 on 64-bit, 28 on 32-bit.
        var msg = Buffer.alloc(process.arch === "x64" ? 48 : 28);

        // Unable to use GetMessage because it blocks, and can't call via .async because it needs to be in the same
        // thread as the window.
        while (gpii.windows.user32.PeekMessageW(msg, 0, 0, 0, 1)) {
            gpii.windows.user32.TranslateMessage(msg);
            gpii.windows.user32.DispatchMessageW(msg);
        }

        if (that.messageWindow) {
            setTimeout(loop, gpii.windows.messages.messagePumpDelay);
        }
    };
    loop();
};

/** Delay in ms for polling messages. Only applies when running as gpii-windows. */
gpii.windows.messages.messagePumpDelay = 2000;

/**
 * Creates a window whose only purpose is to receive messages.
 *
 * The window has no visibility - it just exists.
 *
 * @param {Component} that The gpii.windows.messages instance.
 */
gpii.windows.messages.createMessageWindow = function (that) {
    if (that.messageWindow) {
        fluid.fail("Message window already created.");
    }

    var className = windows.stringToWideChar(that.windowClassname);

    // Window class only needs to be registered once.
    if (!gpii.windows.messages.windowProcPointer) {
        // Create the Window Class for the window
        var cls = new windows.WNDCLASSW();
        cls.ref().fill(0);

        cls.lpszClassName = className;

        // Create a pointer to the window procedure function.
        var callback = ffi.Callback(gpii.windows.types.HANDLE,
            [gpii.windows.types.HANDLE, gpii.windows.types.UINT, gpii.windows.types.UINT, gpii.windows.types.PVOID],
            function (hwnd, msg, wParam, lParam) {
                return gpii.windows.messages.windowProc(that, hwnd, msg, wParam, lParam);
            });
        cls.lpfnWndProc = callback;

        // Keep a reference to the function pointer, otherwise the GC will free it. Because 'callback' points to the
        // function that windows calls whenever there is a window message, the buffers need to be kept alive for as long
        // as GPII is running.
        // Additionally, this value also contains the "cif" buffer (internal to ffi-napi) which also needs to be
        // referenced. See GPII-3445.
        gpii.windows.messages.windowProcPointer = function () {
            callback();
        };

        var result = gpii.windows.user32.RegisterClassW(cls.ref());
        if (!result) {
            fluid.fail("RegisterClass failed");
        }
    }

    // Start the message loop
    if (!process.versions.electron) {
        that.messageWindow = 1; // make the loop think there's already a window.
        gpii.windows.messages.messagePump(that);
    }

    // Create the Window.
    that.messageWindow =
        gpii.windows.user32.CreateWindowExW(0, className, className, 0, 0, 0, 0, 0, 0, 0, 0, 0);

    if (!that.messageWindow) {
        fluid.fail("CreateWindowEx failed");
    }
};

/**
 * Destroys the window created by createMessageWindow.
 *
 * @param {Component} that The gpii.windows.messages instance.
 * @return {Boolean} false on failure.
 */
gpii.windows.messages.destroyMessageWindow = function (that) {
    var success = true;
    if (that.messageWindow) {
        // Destroy the window.
        success = !!windows.user32.DestroyWindow(that.messageWindow);
        that.messageWindow = null;
        that.references = [];
    }
    return success;
};

/**
 * Send a message to a window, returning a promise which resolve when the message has been processed.
 *
 * @param {Number} hwnd The window handle.
 * @param {Number} msg The message, or the name of a registered message (will be registered if not already).
 * @param {Number} wParam Message parameter.
 * @param {Number} lParam Message parameter.
 * @param {Number} timeout [optional] Number of milliseconds to wait before resolving with "timeout".
 * @return {Promise} Resolves when the message has been processed.
 */
gpii.windows.messages.sendMessageAsync = function (hwnd, msg, wParam, lParam, timeout) {
    var promise = fluid.promise();
    var complete = false;

    var timer = timeout && setTimeout(function () {
        if (!complete) {
            complete = true;
            promise.resolve("timeout");
        }
    }, timeout);

    var t = gpii.windows.types;
    var callback = ffi.Callback(t.BOOL, [t.HANDLE, t.UINT, t.UINT, t.ULONG_PTR, t.ULONG_PTR],
        function (w, m, wp, lp, result) {
            if (!complete) {
                complete = true;
                promise.resolve(result);
            }
            if (timer) {
                clearTimeout(timer);
            }
        });

    gpii.windows.user32.SendMessageCallbackW(hwnd, msg, wParam, lParam, callback, 0);

    // SendMessageCallbackW is async, and will call the callback function beyond the lifetime of this function. This
    // has meant that callback sometimes gets GC'd before it is called. Referring to it in a function that's attached
    // to the promise will keep the callback alive for as long as the promise.
    // https://github.com/node-ffi/node-ffi/issues/72#issuecomment-8599403
    promise.then(function () {
        // A no-op that keeps a reference to callback.
        return callback && null;
    });

    return promise;
};

gpii.windows.messages();
<|endoftext|>


<commit_before>import LabelSmall from '../LabelSmall';
import React from 'react';
import Time from '../TimeStamp';
import styles from './styles.css';
const PropTypes = React.PropTypes;

function LabelPair(props) {
    if (props.co2) {
        return (

                <LabelSmall
                    content={props.points}
                    icon={'icon-brightness_high'}
                    onClick={props.onClick}
                    type={"caption2Strong"}
                />

                    <LabelSmall
                        content={props.co2}
                        icon={'icon-leaf'}
                        onClick={props.onClick}
                        type={"caption2Strong"}
                    />

        );
    }

    if (props.time) {
        return (

                <Time
                    dateTime={props.time}
                    onlyDays={props.onlyDays}
                    short={Boolean(true)}
                />

                    <LabelSmall
                        content={props.points}
                        icon={'icon-brightness_high'}
                        onClick={props.onClick}
                        type={"caption2Strong"}
                    />

        );
    }

    return (

            <LabelSmall
                content={props.points}
                icon={'icon-brightness_high'}
                onClick={props.onClick}
                type={"caption2Strong"}
            />

    );
}

LabelPair.displayName = 'LabelPair';
LabelPair.propTypes = {
    co2: PropTypes.number,
    onClick: PropTypes.func,
    onlyDays: PropTypes.bool,
    points: PropTypes.number,
    time: PropTypes.number
};
export default LabelPair;
<commit_msg>Remove points label - #1724<commit_after>import LabelSmall from '../LabelSmall';
import React from 'react';
import Time from '../TimeStamp';
import styles from './styles.css';
const PropTypes = React.PropTypes;

function LabelPair(props) {
    if (props.co2) {
        return (

                {/*<LabelSmall
                    content={props.points}
                    icon={'icon-brightness_high'}
                    onClick={props.onClick}
                    type={"caption2Strong"}
                />
                */}
                    <LabelSmall
                        content={props.co2}
                        icon={'icon-leaf'}
                        onClick={props.onClick}
                        type={"caption2Strong"}
                    />
                  {/**/}

        );
    }

    if (props.time) {
        return (

                <Time
                    dateTime={props.time}
                    onlyDays={props.onlyDays}
                    short={Boolean(true)}
                />
              {/*
                    <LabelSmall
                        content={props.points}
                        icon={'icon-brightness_high'}
                        onClick={props.onClick}
                        type={"caption2Strong"}
                    />
                */}

        );
    }

    return (

            <LabelSmall
                content={props.points}
                icon={'icon-brightness_high'}
                onClick={props.onClick}
                type={"caption2Strong"}
            />

    );
}

LabelPair.displayName = 'LabelPair';
LabelPair.propTypes = {
    co2: PropTypes.number,
    onClick: PropTypes.func,
    onlyDays: PropTypes.bool,
    points: PropTypes.number,
    time: PropTypes.number
};
export default LabelPair;
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { View, StyleSheet } from 'react-native';
import Backdrop from './Backdrop';
import { debug } from './logger.js';

export default class MenuPlaceholder extends Component {
  constructor(props) {
    super(props)
    this.state = {};
  }

  shouldComponentUpdate() {
    // don't terminate closing animation
    return !this.props.ctx._isMenuClosing;
  }

  render() {
    const { ctx, backdropStyles } = this.props;
    const shouldRenderMenu = ctx.isMenuOpen() && ctx._isInitialized();
    debug('MenuPlaceholder should render', shouldRenderMenu);
    if (!shouldRenderMenu) {
      return null;
    }
    return (
      <View style={styles.placeholder}>
        <Backdrop
          onPress={ctx._onBackdropPress}
          style={backdropStyles}
          ref={ctx.onBackdropRef}
        />
        {
          ctx._makeOptions()
        }
      </View>
    );
  }
}

const styles = StyleSheet.create({
  placeholder: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
  },
});
<commit_msg>fix #145 (by JesseRWeigel) scroll fix<commit_after>import React, { Component } from 'react';
import { View, StyleSheet } from 'react-native';
import Backdrop from './Backdrop';
import { debug } from './logger.js';

export default class MenuPlaceholder extends Component {
  constructor(props) {
    super(props)
    this.state = {};
  }

  shouldComponentUpdate() {
    // don't terminate closing animation
    return !this.props.ctx._isMenuClosing;
  }

  render() {
    const { ctx, backdropStyles } = this.props;
    const shouldRenderMenu = ctx.isMenuOpen() && ctx._isInitialized();
    debug('MenuPlaceholder should render', shouldRenderMenu);
    if (!shouldRenderMenu) {
      return null;
    }
    return (
      <View style={styles.placeholder}>
        <Backdrop
          onPress={ctx._onBackdropPress}
          style={backdropStyles}
          ref={ctx.onBackdropRef}
        />
        {
          ctx._makeOptions()
        }
      </View>
    );
  }
}

const styles = StyleSheet.create({
  placeholder: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    overflowY: 'hidden',
  },
});
<|endoftext|>


<commit_before>import angular from 'angular';
import _ from 'underscore';
import template from './compare.html';
import './compare.scss';

export function StatRangeFilter() {
  // Turns a stat and a list of ranges into a 0-100 scale
  return function(stat, statRanges) {
    if (!stat) {
      return -1;
    }
    const statRange = statRanges[stat.statHash];
    if (stat.qualityPercentage) {
      return stat.qualityPercentage.min;
    }

    if (!statRange.enabled) {
      return -1;
    }

    return 100 * (stat.value - statRange.min) / (statRange.max - statRange.min);
  };
}

export const CompareComponent = {
  controller: CompareCtrl,
  controllerAs: 'vm',
  template
};

function CompareCtrl($scope, toaster, dimCompareService, dimStoreService, D2StoresService, $i18next) {
  'ngInject';

  const vm = this;

  function getStoreService(item) {
    return item.destinyVersion === 2 ? D2StoresService : dimStoreService;
  }

  function addMissingStats(item) {
    if (!vm.comparisons[0]) {
      item.stats.forEach((stat, idx) => {
        vm.statsMap[stat.id] = { index: idx, name: stat.name };
      });
      return item;
    }
    const itemStatsMap = {};
    item.stats.forEach((stat, idx) => {
      itemStatsMap[stat.id] = { index: idx, name: stat.name };
    });

    _.difference(_.keys(vm.statsMap), _.keys(itemStatsMap)).forEach((statId) => {
      item.stats.splice(vm.statsMap[statId].index, 0, {
        value: undefined,
        id: Number(statId),
        statHash: Number(statId),
        name: vm.statsMap[statId].name,
        missingStat: true
      });
      vm.statRanges[statId] = { min: 0, max: 0, enabled: false };
    });

    _.difference(_.keys(itemStatsMap), _.keys(vm.statsMap)).forEach((statId) => {
      _.keys(vm.statsMap).forEach((statMapId) => {
        if (vm.statsMap[statMapId].index >= itemStatsMap[statId].index) {
          vm.statsMap[statMapId].index++;
        }
      });
      vm.statsMap[statId] = itemStatsMap[statId];

      const missingStatItemIdx = vm.comparisons.findIndex((compItem) => !compItem.stats.some((stat) => stat.id === Number(statId)));
      if (missingStatItemIdx >= 0) {
        vm.comparisons[missingStatItemIdx].stats.splice(vm.statsMap[statId].index, 0, {
          value: undefined,
          id: Number(statId),
          statHash: Number(statId),
          name: itemStatsMap[statId].name,
          missingStat: true
        });
      }
    });
    return item;
  }

  function removeMissingStats() {
    _.each(vm.comparisons, (compItem) => {
      const statIndex = compItem.stats.findIndex((stat) => stat.missingStat);
      if (statIndex >= 0) {
        compItem.stats.splice(statIndex, 1);
      }
    });
  }

  vm.show = dimCompareService.dialogOpen;

  vm.comparisons = [];
  vm.statRanges = {};
  vm.statsMap = {};

  $scope.$on('dim-store-item-compare', (event, args) => {
    vm.show = true;
    dimCompareService.dialogOpen = true;

    vm.add(args);
  });

  vm.cancel = function cancel() {
    removeMissingStats();
    vm.comparisons = [];
    vm.statRanges = {};
    vm.statsMap = {};
    vm.similarTypes = [];
    vm.archeTypes = [];
    vm.highlight = null;
    vm.sortedHash = null;
    vm.show = false;
    dimCompareService.dialogOpen = false;
  };

  vm.compareSimilar = function(type) {
    vm.comparisons = type === 'archetype' ? vm.archeTypes : vm.similarTypes;
    vm.comparisons.forEach(addMissingStats);
  };

  vm.sort = function(statHash) {
    vm.sortedHash = statHash;
    vm.comparisons = _.sortBy(_.sortBy(_.sortBy(vm.comparisons, 'index'), 'name').reverse(), (item) => {
      const stat = statHash === item.primStat.statHash
        ? item.primStat
        : (vm.sortedHash === 'Rating'
          ? { value: item.dtrRating }
          : _.find(item.stats, { statHash: statHash }));
      return stat.value || -1;
    }).reverse();
  };

  vm.add = function add(args) {
    if (!args.item.equipment) {
      return;
    }

    if (vm.comparisons.length && vm.comparisons[0].typeName && args.item.typeName !== vm.comparisons[0].typeName) {
      if (vm.comparisons[0].classType && args.item.classType !== vm.comparisons[0].classType) {
        toaster.pop('warning', args.item.name, $i18next.t('Compare.Error.Class', { class: vm.comparisons[0].classTypeNameLocalized }));
        return;
      }
      toaster.pop('warning', args.item.name, $i18next.t('Compare.Error.Archetype', { type: vm.comparisons[0].typeName }));
      return;
    }

    if (args.dupes) {
      vm.compare = args.item;
      const allItems = getStoreService(args.item).getAllItems();
      vm.similarTypes = _.filter(allItems, { typeName: vm.compare.typeName });
      let armorSplit;
      if (!vm.compare.location.inWeapons) {
        vm.similarTypes = _.filter(vm.similarTypes, { classType: vm.compare.classType });
        armorSplit = _.reduce(vm.compare.stats, (memo, stat) => {
          return memo + (stat.base === 0 ? 0 : stat.statHash);
        }, 0);
      }

      // 4284893193 is RPM in D2
      const archetypeStat = _.find(vm.compare.stats, {
        statHash: (vm.compare.destinyVersion === 1
          ? vm.compare.stats[0].statHash
          : 4284893193)
      });
      if (archetypeStat) {
        vm.archeTypes = _.filter(vm.similarTypes, (item) => {
          if (item.location.inWeapons) {
            const archetypeMatch = _.find(item.stats, {
              statHash: (vm.compare.destinyVersion === 1
                ? vm.compare.stats[0].statHash
                : 4284893193)
            });
            if (!archetypeMatch) {
              return false;
            }
            return archetypeMatch.base === archetypeStat.base;
          }
          return _.reduce(item.stats, (memo, stat) => {
            return memo + (stat.base === 0 ? 0 : stat.statHash);
          }, 0) === armorSplit;
        });
      }
      vm.comparisons = _.map(_.filter(allItems, { hash: vm.compare.hash }), addMissingStats);
    } else if (!_.findWhere(vm.comparisons, { hash: args.item.hash, id: args.item.id })) {
      addMissingStats(args.item);
      vm.comparisons.push(args.item);
    }
  };

  vm.remove = function remove(item) {
    vm.comparisons = vm.comparisons.filter((compare) => {
      return compare.index !== item.index;
    });

    if (!vm.comparisons.length) {
      vm.cancel();
    }
  };

  vm.itemClick = function itemClick(item) {
    const element = angular.element(document.getElementById(item.index));
    if (!element || !element[0]) {
      throw new Error(`No element with id ${item.index}`)
    }
    const elementRect = element[0].getBoundingClientRect();
    const absoluteElementTop = elementRect.top + window.pageYOffset;
    window.scrollTo(0, absoluteElementTop - 150);
    element.addClass('item-pop');
    element.on('webkitAnimationEnd oanimationend msAnimationEnd animationend', () => {
      element.removeClass('item-pop');
    });
  };

  $scope.$watch('vm.comparisons', () => {
    const statBuckets = {};

    function bucketStat(stat) {
      if (stat && stat.value) {
        (statBuckets[stat.statHash] = statBuckets[stat.statHash] || []).push(stat.value);
      }
    }

    vm.comparisons.forEach((item) => {
      if (item.stats) {
        item.stats.forEach(bucketStat);
        bucketStat(item.primStat);
        bucketStat({ statHash: 0, value: item.dtrRating });
      }
    });

    vm.statRanges = {};
    _.each(statBuckets, (bucket, hash) => {
      const statRange = {
        min: Math.min(...bucket),
        max: Math.max(...bucket)
      };
      statRange.enabled = statRange.min !== statRange.max;
      vm.statRanges[hash] = statRange;
    });
  }, true);
}
<commit_msg>Another fix<commit_after>import angular from 'angular';
import _ from 'underscore';
import template from './compare.html';
import './compare.scss';

export function StatRangeFilter() {
  // Turns a stat and a list of ranges into a 0-100 scale
  return function(stat, statRanges) {
    if (!stat) {
      return -1;
    }
    const statRange = statRanges[stat.statHash];
    if (stat.qualityPercentage) {
      return stat.qualityPercentage.min;
    }

    if (!statRange || !statRange.enabled) {
      return -1;
    }

    return 100 * (stat.value - statRange.min) / (statRange.max - statRange.min);
  };
}

export const CompareComponent = {
  controller: CompareCtrl,
  controllerAs: 'vm',
  template
};

function CompareCtrl($scope, toaster, dimCompareService, dimStoreService, D2StoresService, $i18next) {
  'ngInject';

  const vm = this;

  function getStoreService(item) {
    return item.destinyVersion === 2 ? D2StoresService : dimStoreService;
  }

  function addMissingStats(item) {
    if (!vm.comparisons[0]) {
      item.stats.forEach((stat, idx) => {
        vm.statsMap[stat.id] = { index: idx, name: stat.name };
      });
      return item;
    }
    const itemStatsMap = {};
    item.stats.forEach((stat, idx) => {
      itemStatsMap[stat.id] = { index: idx, name: stat.name };
    });

    _.difference(_.keys(vm.statsMap), _.keys(itemStatsMap)).forEach((statId) => {
      item.stats.splice(vm.statsMap[statId].index, 0, {
        value: undefined,
        id: Number(statId),
        statHash: Number(statId),
        name: vm.statsMap[statId].name,
        missingStat: true
      });
      vm.statRanges[statId] = { min: 0, max: 0, enabled: false };
    });

    _.difference(_.keys(itemStatsMap), _.keys(vm.statsMap)).forEach((statId) => {
      _.keys(vm.statsMap).forEach((statMapId) => {
        if (vm.statsMap[statMapId].index >= itemStatsMap[statId].index) {
          vm.statsMap[statMapId].index++;
        }
      });
      vm.statsMap[statId] = itemStatsMap[statId];

      const missingStatItemIdx = vm.comparisons.findIndex((compItem) => !compItem.stats.some((stat) => stat.id === Number(statId)));
      if (missingStatItemIdx >= 0) {
        vm.comparisons[missingStatItemIdx].stats.splice(vm.statsMap[statId].index, 0, {
          value: undefined,
          id: Number(statId),
          statHash: Number(statId),
          name: itemStatsMap[statId].name,
          missingStat: true
        });
      }
    });
    return item;
  }

  function removeMissingStats() {
    _.each(vm.comparisons, (compItem) => {
      const statIndex = compItem.stats.findIndex((stat) => stat.missingStat);
      if (statIndex >= 0) {
        compItem.stats.splice(statIndex, 1);
      }
    });
  }

  vm.show = dimCompareService.dialogOpen;

  vm.comparisons = [];
  vm.statRanges = {};
  vm.statsMap = {};

  $scope.$on('dim-store-item-compare', (event, args) => {
    vm.show = true;
    dimCompareService.dialogOpen = true;

    vm.add(args);
  });

  vm.cancel = function cancel() {
    removeMissingStats();
    vm.comparisons = [];
    vm.statRanges = {};
    vm.statsMap = {};
    vm.similarTypes = [];
    vm.archeTypes = [];
    vm.highlight = null;
    vm.sortedHash = null;
    vm.show = false;
    dimCompareService.dialogOpen = false;
  };

  vm.compareSimilar = function(type) {
    vm.comparisons = type === 'archetype' ? vm.archeTypes : vm.similarTypes;
    vm.comparisons.forEach(addMissingStats);
  };

  vm.sort = function(statHash) {
    vm.sortedHash = statHash;
    vm.comparisons = _.sortBy(_.sortBy(_.sortBy(vm.comparisons, 'index'), 'name').reverse(), (item) => {
      const stat = statHash === item.primStat.statHash
        ? item.primStat
        : (vm.sortedHash === 'Rating'
          ? { value: item.dtrRating }
          : _.find(item.stats, { statHash: statHash }));
      return stat.value || -1;
    }).reverse();
  };

  vm.add = function add(args) {
    if (!args.item.equipment) {
      return;
    }

    if (vm.comparisons.length && vm.comparisons[0].typeName && args.item.typeName !== vm.comparisons[0].typeName) {
      if (vm.comparisons[0].classType && args.item.classType !== vm.comparisons[0].classType) {
        toaster.pop('warning', args.item.name, $i18next.t('Compare.Error.Class', { class: vm.comparisons[0].classTypeNameLocalized }));
        return;
      }
      toaster.pop('warning', args.item.name, $i18next.t('Compare.Error.Archetype', { type: vm.comparisons[0].typeName }));
      return;
    }

    if (args.dupes) {
      vm.compare = args.item;
      const allItems = getStoreService(args.item).getAllItems();
      vm.similarTypes = _.filter(allItems, { typeName: vm.compare.typeName });
      let armorSplit;
      if (!vm.compare.location.inWeapons) {
        vm.similarTypes = _.filter(vm.similarTypes, { classType: vm.compare.classType });
        armorSplit = _.reduce(vm.compare.stats, (memo, stat) => {
          return memo + (stat.base === 0 ? 0 : stat.statHash);
        }, 0);
      }

      // 4284893193 is RPM in D2
      const archetypeStat = _.find(vm.compare.stats, {
        statHash: (vm.compare.destinyVersion === 1
          ? vm.compare.stats[0].statHash
          : 4284893193)
      });
      if (archetypeStat) {
        vm.archeTypes = _.filter(vm.similarTypes, (item) => {
          if (item.location.inWeapons) {
            const archetypeMatch = _.find(item.stats, {
              statHash: (vm.compare.destinyVersion === 1
                ? vm.compare.stats[0].statHash
                : 4284893193)
            });
            if (!archetypeMatch) {
              return false;
            }
            return archetypeMatch.base === archetypeStat.base;
          }
          return _.reduce(item.stats, (memo, stat) => {
            return memo + (stat.base === 0 ? 0 : stat.statHash);
          }, 0) === armorSplit;
        });
      }
      vm.comparisons = _.map(_.filter(allItems, { hash: vm.compare.hash }), addMissingStats);
    } else if (!_.findWhere(vm.comparisons, { hash: args.item.hash, id: args.item.id })) {
      addMissingStats(args.item);
      vm.comparisons.push(args.item);
    }
  };

  vm.remove = function remove(item) {
    vm.comparisons = vm.comparisons.filter((compare) => {
      return compare.index !== item.index;
    });

    if (!vm.comparisons.length) {
      vm.cancel();
    }
  };

  vm.itemClick = function itemClick(item) {
    const element = angular.element(document.getElementById(item.index));
    if (!element || !element[0]) {
      throw new Error(`No element with id ${item.index}`)
    }
    const elementRect = element[0].getBoundingClientRect();
    const absoluteElementTop = elementRect.top + window.pageYOffset;
    window.scrollTo(0, absoluteElementTop - 150);
    element.addClass('item-pop');
    element.on('webkitAnimationEnd oanimationend msAnimationEnd animationend', () => {
      element.removeClass('item-pop');
    });
  };

  $scope.$watch('vm.comparisons', () => {
    const statBuckets = {};

    function bucketStat(stat) {
      if (stat && stat.value) {
        (statBuckets[stat.statHash] = statBuckets[stat.statHash] || []).push(stat.value);
      }
    }

    vm.comparisons.forEach((item) => {
      if (item.stats) {
        item.stats.forEach(bucketStat);
        bucketStat(item.primStat);
        bucketStat({ statHash: 0, value: item.dtrRating });
      }
    });

    vm.statRanges = {};
    _.each(statBuckets, (bucket, hash) => {
      const statRange = {
        min: Math.min(...bucket),
        max: Math.max(...bucket)
      };
      statRange.enabled = statRange.min !== statRange.max;
      vm.statRanges[hash] = statRange;
    });
  }, true);
}
<|endoftext|>


<commit_before>//import //

var _elm_lang$core$Native_Platform = function() {

// BAGS

function leaf(home)
{
	return function(value)
	{
		return {
			type: 'leaf',
			home: home,
			value: value
		};
	};
}

function batch(list)
{
	return {
		type: 'node',
		branches: list
	};
}

function map(tagger, bag)
{
	return {
		type: 'map',
		tagger: tagger,
		tree: bag
	}
}

function categorizeEffects(bag)
{
	return categorizeEffectsHelp(bag, {});
}

function categorizeEffectsHelp(bag, effectDict)
{
	switch (bag.type)
	{
		case 'leaf':
			var home = bag.home;
			var effects = effectDict[home];

			var value = {
				effect: bag.value,
				taggers: []
			};

			effects
				? effects.push(value)
				: effectDict[home] = [value];

			return effectDict;

		case 'node':
			var list = bag.branches;
			while (list.ctor !== '[]')
			{
				categorizeEffectsHelp(list._0, effectDict);
				list = list._1;
			}
			return effectDict;

		case 'map':
			categorizeEffectsHelp(bag.tree, effectDict);
			for (var home in effectDict)
			{
				var effects = effectDict[home];
				for (var i = 0; i < effects.length; i++)
				{
					var effect = effects[i];
					effect.taggers.push(bag.tagger);
				}
			}
			return effectDict;
	}
}

// PROGRAMS

function fullscreenFor(program)
{
	if (!program.renderer)
	{
		return function fullscreen()
		{
			_evancz$virtual_dom$Native_VirtualDom.render(document.body, program);
		}
	}

	return function fullscreen(flags)
	{
		var update = program.update;
		var subs = program.subscriptions;
		var view = program.view;

		var tuple = program.init(flags);
		var model = tuple._0;
		var initialCmds = categorizeEffects(tuple._1);
		var initialSubs = categorizeEffects(subs(model));

		var renderer = program.renderer(document.body, enqueue, view(model));

		function enqueue(msg)
		{
			// TODO this may be mean user events can "cut" to the front
			// of the event queue. If so, do it another way instead.
			var tuple = A2(update, msg, model);
			model = tuple._0;
			categorizeEffects(tuple._1);
			renderer.update(view(model));
		}
	};
}

function mainIsUndefined(name)
{
	return function() {
		throw new Error('Cannot initialize module ' + name + ' because it has no `main` value! What would I run?')
	};
}

function addPublicModule(object, name, main)
{
	object['fullscreen'] = main ? fullscreenFor(main) : mainIsUndefined(name);
}

return {
	addPublicModule: addPublicModule,
	leaf: leaf,
	batch: batch,
	map: map
};

}();<commit_msg>Update for rename of VirtualDom function<commit_after>//import //

var _elm_lang$core$Native_Platform = function() {

// BAGS

function leaf(home)
{
	return function(value)
	{
		return {
			type: 'leaf',
			home: home,
			value: value
		};
	};
}

function batch(list)
{
	return {
		type: 'node',
		branches: list
	};
}

function map(tagger, bag)
{
	return {
		type: 'map',
		tagger: tagger,
		tree: bag
	}
}

function categorizeEffects(bag)
{
	return categorizeEffectsHelp(bag, {});
}

function categorizeEffectsHelp(bag, effectDict)
{
	switch (bag.type)
	{
		case 'leaf':
			var home = bag.home;
			var effects = effectDict[home];

			var value = {
				effect: bag.value,
				taggers: []
			};

			effects
				? effects.push(value)
				: effectDict[home] = [value];

			return effectDict;

		case 'node':
			var list = bag.branches;
			while (list.ctor !== '[]')
			{
				categorizeEffectsHelp(list._0, effectDict);
				list = list._1;
			}
			return effectDict;

		case 'map':
			categorizeEffectsHelp(bag.tree, effectDict);
			for (var home in effectDict)
			{
				var effects = effectDict[home];
				for (var i = 0; i < effects.length; i++)
				{
					var effect = effects[i];
					effect.taggers.push(bag.tagger);
				}
			}
			return effectDict;
	}
}

// PROGRAMS

function fullscreenFor(program)
{
	if (!program.renderer)
	{
		return function fullscreen()
		{
			_evancz$virtual_dom$Native_VirtualDom.staticProgram(document.body, program);
		}
	}

	return function fullscreen(flags)
	{
		var update = program.update;
		var subs = program.subscriptions;
		var view = program.view;

		var tuple = program.init(flags);
		var model = tuple._0;
		var initialCmds = categorizeEffects(tuple._1);
		var initialSubs = categorizeEffects(subs(model));

		var renderer = program.renderer(document.body, enqueue, view(model));

		function enqueue(msg)
		{
			// TODO this may be mean user events can "cut" to the front
			// of the event queue. If so, do it another way instead.
			var tuple = A2(update, msg, model);
			model = tuple._0;
			categorizeEffects(tuple._1);
			renderer.update(view(model));
		}
	};
}

function mainIsUndefined(name)
{
	return function() {
		throw new Error('Cannot initialize module ' + name + ' because it has no `main` value! What would I run?')
	};
}

function addPublicModule(object, name, main)
{
	object['fullscreen'] = main ? fullscreenFor(main) : mainIsUndefined(name);
}

return {
	addPublicModule: addPublicModule,
	leaf: leaf,
	batch: batch,
	map: map
};

}();<|endoftext|>


<commit_before>/**
 * MilestoneController
 *
 * @module      :: Controller
 * @description :: Contains logic for handling requests.
 */

module.exports = {
    /**
     * Milestone add action.
     *
     * @param   {Request}   req Request object
     * @param   {Response}  res Response object
     */
    add: function(req, res) {
        var projectId = parseInt(req.param("projectId"), 10);

        // Fetch user role and project data
        async.parallel(
            {
                // Determine user role in this project
                role: function(callback) {
                    AuthService.hasProjectAccess(req.user, projectId, callback, true);
                },
                // Fetch project data
                project: function(callback) {
                    DataService.getProject(projectId, callback);
                }
            },
            function (error, data) {
                if (error) {
                    res.send(error, error.status ? error.status : 500);
                } else {
                    data.layout = req.isAjax ? "layout_ajax" : "layout";
                    data.projectId = projectId;

                    res.view(data);
                }
            }
        );
    },

    /**
     * Milestone edit action.
     *
     * @param   {Request}   req Request object
     * @param   {Response}  res Response object
     */
    edit: function(req, res) {
        var milestoneId = parseInt(req.param("id"), 10);

        // Fetch role and milestone data
        async.parallel(
            {
                // Determine user role in this milestone
                role: function(callback) {
                    AuthService.hasMilestoneAccess(req.user, milestoneId, callback, true);
                },

                // Fetch milestone data
                milestone: function(callback) {
                    DataService.getMilestone(milestoneId, callback)
                }
            },
            function (error, data) {
                if (error) {
                    res.send(error, error.status ? error.status : 500);
                } else {
                    data.layout = req.isAjax ? "layout_ajax" : "layout";

                    res.view(data);
                }
            }
        );
    },

    /**
     * Milestone stories action.
     *
     * @todo refactor this later on...
     *
     * @param   {Request}   req Request object
     * @param   {Response}  res Response object
     */
    stories: function(req, res) {
        var milestoneId = parseInt(req.param("id"), 10);

        var data = {
            layout: req.isAjax ? "layout_ajax" : "layout",
            role: 0,
            milestone: {
                data: false,
                progressStory: 0,
                progressTask: 0,
                cntStoryDone: 0,
                cntStoryTotal: 0,
                cntTaskDone: 0,
                cntTaskTotal: 0
            },
            stories: false
        };

        // Fetch role and milestone data
        async.parallel(
            {
                // Determine user role in this milestone
                role: function(callback) {
                    AuthService.hasMilestoneAccess(req.user, milestoneId, callback, true);
                },

                // Fetch milestone data
                milestone: function(callback) {
                    DataService.getMilestone(milestoneId, callback)
                }
            },
            function (error, results) {
                if (error) {
                    res.send(error, error.status ? error.status : 500);
                } else {
                    data.role = results.role;
                    data.milestone.data = results.milestone;

                    // Find all user stories which are attached to milestone
                    Story
                        .find()
                        .where({
                            milestoneId: data.milestone.data.id
                        })
                        .sort('title ASC')
                        .done(function(error, stories) {
                            data.stories = stories;

                            fetchTasks();
                        });
                }
            }
        );

        /**
         * Function to fetch tasks of stories in current milestone.
         *
         * @todo refactor all this shit later
         */
        function fetchTasks() {
            // We have no stories, so make view
            if (data.stories.length === 0) {
                makeView();
            } else {
                data.milestone.cntStoryTotal = data.stories.length;
                data.milestone.cntStoryDone = _.reduce(data.stories, function (memo, story) {
                    return (story.isDone) ? memo + 1 : memo;
                }, 0);

                if (data.milestone.cntStoryDone > 0) {
                    data.milestone.progressStory = Math.round(data.milestone.cntStoryDone  / data.milestone.cntStoryTotal * 100);
                }

                // Iterate stories
                _.each(data.stories, function(/** sails.model.story */story) {
                    // Initialize milestone stories property
                    story.tasks = false;

                    // Find all user stories which are attached to current milestone
                    Task
                        .find()
                        .where({
                            storyId: story.id
                        })
                        .sort('title ASC')
                        .done(function(error, tasks) {
                            // Add tasks to story data
                            story.tasks = tasks;

                            story.doneTasks = _.reduce(tasks, function (memo, task) {
                                return (task.isDone) ? memo + 1 : memo;
                            }, 0);

                            // Add milestone task counter
                            data.milestone.cntTaskTotal = data.milestone.cntTaskTotal + story.tasks.length;
                            data.milestone.cntTaskDone = data.milestone.cntTaskDone + story.doneTasks;

                            if (story.doneTasks > 0) {
                                story.progress = Math.round(story.doneTasks / tasks.length * 100);
                            } else {
                                story.progress = 0;
                            }

                            // Call view
                            makeView();
                        });
                });
            }
        }

        /**
         * Function to make actual view for milestone edit.
         *
         * @todo this needs some real refactoring later on...
         */
        function makeView() {
            if (data.stories.length > 0) {
                var show = true;

                _.each(data.stories, function(/** sails.model.story */story) {
                    if (story.tasks === false) {
                        show = false;
                    }
                });

                if (show) {
                    if (data.milestone.cntTaskDone > 0) {
                        data.milestone.progressTask = Math.round(data.milestone.cntTaskDone  / data.milestone.cntTaskTotal * 100);
                    }

                    res.view(data);
                }
            } else {
                res.view(data);
            }
        }
    }
};
<commit_msg>More refactor of MilestoneController.<commit_after>/**
 * MilestoneController
 *
 * @module      :: Controller
 * @description :: Contains logic for handling requests.
 */
"use strict";

var async = require("async");

module.exports = {
    /**
     * Milestone add action.
     *
     * @param   {Request}   req Request object
     * @param   {Response}  res Response object
     */
    add: function(req, res) {
        var projectId = parseInt(req.param("projectId"), 10);

        // Fetch user role and project data
        async.parallel(
            {
                // Determine user role in this project
                role: function(callback) {
                    AuthService.hasProjectAccess(req.user, projectId, callback, true);
                },
                // Fetch project data
                project: function(callback) {
                    DataService.getProject(projectId, callback);
                }
            },

            /**
             * Main callback function which is called after all parallel jobs are processed.
             *
             * @param   {Error} error   Possible error object
             * @param   {{}}    data    Data object that contains 'role' and 'project' data
             */
            function(error, data) {
                if (error) {
                    res.send(error.status ? error.status : 500, error.message ? error.message : error);
                } else {
                    data.layout = req.isAjax ? "layout_ajax" : "layout";
                    data.projectId = projectId;

                    res.view(data);
                }
            }
        );
    },

    /**
     * Milestone edit action.
     *
     * @param   {Request}   req Request object
     * @param   {Response}  res Response object
     */
    edit: function(req, res) {
        var milestoneId = parseInt(req.param("id"), 10);

        // Fetch role and milestone data
        async.parallel(
            {
                // Determine user role in this milestone
                role: function(callback) {
                    AuthService.hasMilestoneAccess(req.user, milestoneId, callback, true);
                },

                // Fetch milestone data
                milestone: function(callback) {
                    DataService.getMilestone(milestoneId, callback)
                }
            },

            /**
             * Callback function which is called after all parallel jobs are processed.
             *
             * @param   {Error} error   Possible error object
             * @param   {{}}    data    Data object that contains 'role' and 'milestone' data
             */
            function (error, data) {
                if (error) {
                    res.send(error.status ? error.status : 500, error.message ? error.message : error);
                } else {
                    data.layout = req.isAjax ? "layout_ajax" : "layout";

                    res.view(data);
                }
            }
        );
    },

    /**
     * Milestone stories action.
     *
     * @param   {Request}   req Request object
     * @param   {Response}  res Response object
     */
    stories: function(req, res) {
        var milestoneId = parseInt(req.param("id"), 10);

        var data = {
            layout: req.isAjax ? "layout_ajax" : "layout",
            role: 0,
            milestone: {
                data: false,
                progressStory: 0,
                progressTask: 0,
                cntStoryDone: 0,
                cntStoryTotal: 0,
                cntTaskDone: 0,
                cntTaskTotal: 0
            },
            stories: false
        };

        // Fetch role and milestone data
        async.parallel(
            {
                // Determine user role in this milestone
                role: function(callback) {
                    AuthService.hasMilestoneAccess(req.user, milestoneId, callback, true);
                },

                // Fetch milestone data
                milestone: function(callback) {
                    DataService.getMilestone(milestoneId, callback)
                }
            },

            /**
             * Callback function which is called after all parallel jobs are processed.
             *
             * @param   {Error} error   Possible error object
             * @param   {{}}    results Data object that contains 'role' and 'milestone' data
             */
            function (error, results) {
                if (error) {
                    res.send(error.status ? error.status : 500, error.message ? error.message : error);
                } else {
                    data.role = results.role;
                    data.milestone.data = results.milestone;

                    // Find all user stories which are attached to milestone
                    Story
                        .find()
                        .where({
                            milestoneId: data.milestone.data.id
                        })
                        .sort('title ASC')
                        .done(function(error, stories) {
                            data.stories = stories;

                            fetchTasks();
                        });
                }
            }
        );

        /**
         * Private function to fetch tasks of stories in current milestone.
         */
        function fetchTasks() {
            data.milestone.cntStoryTotal = data.stories.length;
            data.milestone.cntStoryDone = _.reduce(data.stories, function (memo, story) {
                return (story.isDone) ? memo + 1 : memo;
            }, 0);

            if (data.milestone.cntStoryDone > 0) {
                data.milestone.progressStory = Math.round(data.milestone.cntStoryDone  / data.milestone.cntStoryTotal * 100);
            }

            // Map each stories and fetch task data for those
            async.map(
                data.stories,

                /**
                 * Iterator function to process single story object and fetch task data for that.
                 *
                 * @param   {sails.model.story} story       Story object
                 * @param   {Function}          callback    Callback function to call after story is processed
                 */
                function(story, callback) {
                    Task
                        .find()
                        .where({
                            storyId: story.id
                        })
                        .sort('title ASC')
                        .done(function(error, tasks) {
                            if (error) {
                                callback(error, null);
                            } else {
                                // Add tasks to story data
                                story.tasks = tasks;

                                story.doneTasks = _.reduce(tasks, function (memo, task) {
                                    return (task.isDone) ? memo + 1 : memo;
                                }, 0);

                                // Add milestone task counter
                                data.milestone.cntTaskTotal = data.milestone.cntTaskTotal + story.tasks.length;
                                data.milestone.cntTaskDone = data.milestone.cntTaskDone + story.doneTasks;

                                if (story.doneTasks > 0) {
                                    story.progress = Math.round(story.doneTasks / tasks.length * 100);
                                } else {
                                    story.progress = 0;
                                }

                                callback(null, true);
                            }
                        });
                },

                /**
                 * Callback function which is call after are stories are processed.
                 *
                 * @param   {Error}     error   Possible error object
                 * @param   {Boolean}   result  Result data
                 */
                function(error, result) {
                    if (data.milestone.cntTaskDone > 0) {
                        data.milestone.progressTask = Math.round(data.milestone.cntTaskDone  / data.milestone.cntTaskTotal * 100);
                    }

                    res.view(data);
                }
            );
        }
    }
};
<|endoftext|>


<commit_before>const verbose = require('debug')('ha:door:verbose')
const info = require('debug')('ha:door:info')
const error = require('debug')('ha:door:error')

const config = require('./config')
const diehard = require('diehard')
const PIN_DOOR_STATE = config.pins.readDoorState
const Promise = require('bluebird')
const gpio = require('rpi-gpio')
const gpiop = gpio.promise
const request = require('http-as-promised')
const url = require('url')
const JWTGenerator = require('jwt-generator')
const jwtGenerator = new JWTGenerator({
  loginUrl: config.loginUrl,
  privateKey: config.privateKey,
  useRetry: true
})

let intervalHandler

class Door {
  _sendState (state, isRetry = false) {
    const self = this

    info('sendState called.', 'state:', state, 'isRetry:', isRetry)
    return Promise
      .try(() => {
        return isRetry
          ? jwtGenerator.makeNewToken({subject: '/doors', audience: 'urn:home-automation/garage'})
          : jwtGenerator.makeToken({subject: '/doors', audience: 'urn:home-automation/garage'})
      })
      .then(token => {
        info('token generated.  token:', !!token)
        return request({
          uri: url.resolve(config.serverUrl, 'states'),
          method: 'POST',
          auth: {
            bearer: token
          },
          form: {is_open: state === 1}
        })
      })
      .then(() => {
        info('Server state update complete successfully.')
        self.lastState = state
      })
      .catch(err => {
        error('Error while updating state. err:', err)
        return Promise
          .delay(1000)
          .then(() => self._sendState(state, true))
      })
      .finally(() => {
        self.isUpdatingServer = false
      })
  }

  monitor () {
    info('monitor called.')
    const self = this
    return gpiop
      .setup(PIN_DOOR_STATE, gpio.DIR_IN)
      .then(() => {
        info('opened PIN_DOOR_STATE')
        diehard.register(done => {
          if (intervalHandler) {
            clearInterval(intervalHandler)
            intervalHandler = null
          }
          info('closing PIN_DOOR_STATE')
          gpiop
            .destroy(PIN_DOOR_STATE)
            .then(() => {
              info('closed PIN_DOOR_STATE')
              done()
            })
        })
      })
      .then(() => {
        intervalHandler = setInterval(() => {
          verbose('setInterval called.')
          gpiop
            .read(PIN_DOOR_STATE)
            .then(state => {
              verbose('read state:', state)
              if (self.lastState === state || self.isUpdatingServer) {
                verbose('state has not changed.  Returning.',
                  'lastState:', self.lastState,
                  'state:', state,
                  'isUpdatingServer:', self.isUpdatingServer)
                return
              }
              self.isUpdatingServer = true
              info('Door state changed.',
                'State: ', state ? 'Open' : 'Close')

              return self._sendState(state)
            })
            .catch(err => error('#7', err))
        }, 1000)
      })
  }
}

module.exports = Door
<commit_msg>log door state<commit_after>const verbose = require('debug')('ha:door:verbose')
const info = require('debug')('ha:door:info')
const error = require('debug')('ha:door:error')

const config = require('./config')
const diehard = require('diehard')
const PIN_DOOR_STATE = config.pins.readDoorState
const Promise = require('bluebird')
const gpio = require('rpi-gpio')
const gpiop = gpio.promise
const request = require('http-as-promised')
const url = require('url')
const JWTGenerator = require('jwt-generator')
const jwtGenerator = new JWTGenerator({
  loginUrl: config.loginUrl,
  privateKey: config.privateKey,
  useRetry: true
})

let intervalHandler

class Door {
  _sendState (state, isRetry = false) {
    const self = this

    info('sendState called.', 'state (is open):', state, 'isRetry:', isRetry)
    return Promise
      .try(() => {
        return isRetry
          ? jwtGenerator.makeNewToken({subject: '/doors', audience: 'urn:home-automation/garage'})
          : jwtGenerator.makeToken({subject: '/doors', audience: 'urn:home-automation/garage'})
      })
      .then(token => {
        info('token generated.  token:', !!token)
        return request({
          uri: url.resolve(config.serverUrl, 'states'),
          method: 'POST',
          auth: {
            bearer: token
          },
          form: {is_open: state}
        })
      })
      .then(() => {
        info('Server state update complete successfully.')
        self.lastState = state
      })
      .catch(err => {
        error('Error while updating state. err:', err)
        return Promise
          .delay(1000)
          .then(() => self._sendState(state, true))
      })
      .finally(() => {
        self.isUpdatingServer = false
      })
  }

  monitor () {
    info('monitor called.')
    const self = this
    return gpiop
      .setup(PIN_DOOR_STATE, gpio.DIR_IN)
      .then(() => {
        info('opened PIN_DOOR_STATE')
        diehard.register(done => {
          if (intervalHandler) {
            clearInterval(intervalHandler)
            intervalHandler = null
          }
          info('closing PIN_DOOR_STATE')
          gpiop
            .destroy(PIN_DOOR_STATE)
            .then(() => {
              info('closed PIN_DOOR_STATE')
              done()
            })
        })
      })
      .then(() => {
        intervalHandler = setInterval(() => {
          verbose('setInterval called.')
          gpiop
            .read(PIN_DOOR_STATE)
            .then(state => {
              verbose('read state:', state)
              if (self.lastState === state || self.isUpdatingServer) {
                verbose('state has not changed.  Returning.',
                  'lastState:', self.lastState,
                  'state:', state,
                  'isUpdatingServer:', self.isUpdatingServer)
                return
              }
              self.isUpdatingServer = true
              info('Door state changed.',
                'State: ', state ? 'Open' : 'Close')

              return self._sendState(state)
            })
            .catch(err => error('#7', err))
        }, 1000)
      })
  }
}

module.exports = Door
<|endoftext|>


<commit_before>import Ember from 'ember';

export default Ember.Component.extend({
	tagName: 'g',
  // templateName: 'ember-cli-ember-dvc/components/graph-right-tick',

	transition: 500,

	value: null,

	classNames: ['graph-right-tick'],

	isVisible: function(){
		var value = this.get('value');
		return value !== null && !isNaN(value);
	}.property('value'),

	y: function(){
		var yScale = this.get('graph.yScale');
		var value = this.get('value');
		if(!this.get('isVisible')) {
			return 0;
		}
		return yScale(value);
	}.property('value', 'graph.yScale'),

	transform: function(){
		var y = this.get('y') || 0;
		var graphWidth = this.get('graph.width');
		return 'translate(%@ %@)'.fmt(graphWidth - 6, y - 3);
	}.property('graph.width', 'y'),

	_setup: function(){
		var graph = this.nearestWithProperty('isGraph');
		this.set('graph', graph);
	}.on('init'),

	updateElement: function(){
		var path = this.get('path');
		var transition = this.get('transition');
		var transform = this.get('transform');
		path.transition(transition)
			.attr('transform', transform);
	}.observes('transform'),

	getElement: function(){
		var g = d3.select(this.$()[0]);
		var path = g.selectAll('path').data([0]);
		this.set('path', path);
		this.updateElement();
	}.on('didInsertElement')
});<commit_msg>fix y to account for paddinTop<commit_after>import Ember from 'ember';
import { property, observer } from '../utils/computed-property-helpers';

export default Ember.Component.extend({
	tagName: 'g',

	transition: 500,

	value: null,

	classNames: ['graph-right-tick'],

	isVisible: property('value', function(value){
		return value !== null && !isNaN(value);
	}),

	y: property('value', 'graph.yScale', 'graph.paddingTop', 'isVisible', function(value, yScale, paddingTop, isVisible){
		if(!isVisible) {
			return 0;
		}
		return (yScale(value) + paddingTop) || 0;
	}),

	transform: property('y', 'graph.width', function(y, graphWidth){
		return 'translate(%@ %@)'.fmt(graphWidth - 6, y - 3);
	}),

	_setup: function(){
		var graph = this.nearestWithProperty('isGraph');
		this.set('graph', graph);
	}.on('init'),

	updateElement: observer('path', 'transition', 'transform', function(path, transition, transform){
		path.transition(transition).attr('transform', transform);
	}),

	getElement: function(){
		var g = d3.select(this.$()[0]);
		var path = g.selectAll('path').data([0]);
		this.set('path', path);
		this.updateElement();
	}.on('didInsertElement')
});<|endoftext|>


<commit_before>// @flow

import 'rxjs';
import moment from 'moment';
import get from 'lodash/get';
import { combineEpics } from 'redux-observable';
import { Observable } from 'rxjs/Observable';
import type {
  RequestEpicParam,
  RequestByKeyEpicParam,
  FetchIfNeededEpicParam,
  FetchByKeyIfNeededEpicParam,
} from './type';

/*

*/

/* LOGIC
1. isFetching -> false
2. lastUpdate > cacheDuration -> true
3. paging && itemsEnd -> false
4. !cache -> true
5. paging && fresh && payload !== undefined -> false
6. paging && !paginationFetch.page -> true
7. payload === undefined -> true
8. didInvalidate
*/

const shouldFetchPageIfNeeded = (
  state: Object,
  options: Object,
  action: Object,
) => {
  const lastUpdated = moment(state.lastUpdated);
  if (
    lastUpdated.isValid() &&
    lastUpdated.add(options.cacheDuration, 'seconds').isBefore(moment())
  ) {
    action.params.page = 0; // reset page if data expired
    return true;
  }
  if (state.itemsEnd) {
    return false;
  }
  if (action.params.fresh && state.payload !== undefined) {
    return false;
  }
  if (!get(state, `paginationFetched.${state.page}`)) {
    return true;
  }
  if (state.payload === undefined) {
    return true;
  }
  return state.didInvalidate;
};

const shouldFetchIfNeeded = (
  state: Object,
  options: Object,
  action: Object,
) => {
  if (!state) {
    return true;
  }
  if (state.isFetching) {
    return false;
  }
  if (options.paging) {
    return shouldFetchPageIfNeeded(state, options, action);
  }
  if (!options.cache) {
    return true;
  }
  const lastUpdated = moment(state.lastUpdated);
  if (
    lastUpdated.isValid() &&
    lastUpdated.add(options.cacheDuration, 'seconds').isBefore(moment())
  ) {
    return true;
  }
  if (state.payload === undefined) {
    return true;
  }
  return state.didInvalidate;
};

export const getShouldFetchKeys = (
  state: any,
  keys: any,
  options: Object,
  action: Object,
) => {
  if (keys && keys.constructor === Array) {
    const shouldFetchKeys = [];
    for (let i = 0; i < keys.length; i += 1) {
      const key = keys[i];
      if (shouldFetchIfNeeded(state[key], options, action)) {
        shouldFetchKeys.push(key);
      }
    }
    return shouldFetchKeys;
  }
  if (shouldFetchIfNeeded(state[keys], options, action)) {
    return keys;
  }
  return undefined;
};

const defaultOptions = {
  cache: true,
  cacheDuration: 300,
  paging: false,
  handleParamsPromiseReject: true,
  handleParamsPromiseResolve: true,
};

export const createFetchIfNeededEpic = ({
  ducks,
  options,
}: FetchIfNeededEpicParam) => {
  const { requestTypes, requestActions, selector } = ducks;
  return (action$: any, store: any) =>
    action$
      .ofType(requestTypes.FETCH)
      .filter(action =>
        shouldFetchIfNeeded(selector(store.getState()), options, action),
      )
      .map(action =>
        requestActions.request({
          ...action.params,
          page: get(selector(store.getState()), 'page'),
        }),
      );
};

const shouldContinueFetch = (shouldFetchKeys) => {
  if (shouldFetchKeys && shouldFetchKeys.constructor === Array) {
    return shouldFetchKeys.length;
  }
  return !!shouldFetchKeys;
};

export const createFetchByKeyIfNeededEpic = ({
  ducks,
  mapActionToKey,
  restoreFetchableKeyToAction,
  options,
}: FetchByKeyIfNeededEpicParam) => {
  const { requestTypes, requestActions, selector } = ducks;
  return (action$: any, store: any) =>
    action$
      .ofType(requestTypes.FETCH)
      .map((_action) => {
        const action = {
          ..._action,
        };
        const keys = mapActionToKey(action);
        const shouldFetchKeys = getShouldFetchKeys(
          selector(store.getState()),
          keys,
          options,
          action,
        );
        if (shouldContinueFetch(shouldFetchKeys)) {
          if (restoreFetchableKeyToAction) {
            restoreFetchableKeyToAction(action, shouldFetchKeys);
          }
          action.shouldFetch = true;
        }
        return action;
      })
      .filter(action => action.shouldFetch)
      .map((action) => {
        if (get(options, 'paging')) {
          const result = get(
            selector(store.getState()),
            mapActionToKey(action),
          );
          const page = get(result, 'page') || 0;
          return requestActions.request({
            ...action.params,
            page,
          });
        }
        return requestActions.request(action.params);
      });
};

export const createRequestEpic = ({
  ducks,
  api,
  options,
}: RequestEpicParam) => {
  const { requestTypes, requestActions } = ducks;
  return (action$: any, store: any) =>
    action$.ofType(requestTypes.REQUEST).mergeMap(action =>
      Observable.fromPromise(api(action.params, store))
        .map((data) => {
          if (
            get(action, 'params.resolve') && options.handleParamsPromiseResolve
          ) {
            action.params.resolve();
          }
          return requestActions.success(data, action.params);
        })
        .catch((error) => {
          console.error(error);
          console.error(JSON.stringify(error));
          if (
            get(action, 'params.reject') && options.handleParamsPromiseReject
          ) {
            action.params.reject();
          }
          return Observable.of(requestActions.failure(error, action.params));
        }),
    );
};

export const createRequestIfNeededEpic = ({
  ducks,
  api,
  options,
}: RequestEpicParam) => {
  const mergeOptions = {
    ...defaultOptions,
    ...options,
  };
  const fetchItemsIfNeededEpic = createFetchIfNeededEpic({
    ducks,
    options: mergeOptions,
  });
  const requestEpic = createRequestEpic({ ducks, api, options: mergeOptions });
  return combineEpics(fetchItemsIfNeededEpic, requestEpic);
};

export const createRequestByKeyIfNeededEpic = ({
  ducks,
  api,
  mapActionToKey,
  restoreFetchableKeyToAction,
  options = defaultOptions,
}: RequestByKeyEpicParam) => {
  const mergeOptions = {
    ...defaultOptions,
    ...options,
  };
  const fetchByKeyIfNeededEpic = createFetchByKeyIfNeededEpic({
    ducks,
    mapActionToKey,
    restoreFetchableKeyToAction,
    options: mergeOptions,
  });
  const requestEpic = createRequestEpic({ ducks, api, options: mergeOptions });
  return combineEpics(fetchByKeyIfNeededEpic, requestEpic);
};
<commit_msg>added createCacheEvictEpic<commit_after>// @flow

import 'rxjs';
import moment from 'moment';
import get from 'lodash/get';
import isArray from 'lodash/isArray';
import { combineEpics } from 'redux-observable';
import { Observable } from 'rxjs/Observable';
import type {
  Ducks,
  RequestEpicParam,
  RequestByKeyEpicParam,
  FetchIfNeededEpicParam,
  FetchByKeyIfNeededEpicParam,
} from './type';

/*

*/

/* LOGIC
1. isFetching -> false
2. lastUpdate > cacheDuration -> true
3. paging && itemsEnd -> false
4. !cache -> true
5. paging && fresh && payload !== undefined -> false
6. paging && !paginationFetch.page -> true
7. payload === undefined -> true
8. didInvalidate
*/

const shouldFetchPageIfNeeded = (
  state: Object,
  options: Object,
  action: Object,
) => {
  const lastUpdated = moment(state.lastUpdated);
  if (
    lastUpdated.isValid() &&
    lastUpdated.add(options.cacheDuration, 'seconds').isBefore(moment())
  ) {
    action.params.page = 0; // reset page if data expired
    return true;
  }
  if (state.itemsEnd) {
    return false;
  }
  if (action.params.fresh && state.payload !== undefined) {
    return false;
  }
  if (!get(state, `paginationFetched.${state.page}`)) {
    return true;
  }
  if (state.payload === undefined) {
    return true;
  }
  return state.didInvalidate;
};

const shouldFetchIfNeeded = (
  state: Object,
  options: Object,
  action: Object,
) => {
  if (!state) {
    return true;
  }
  if (state.isFetching) {
    return false;
  }
  if (options.paging) {
    return shouldFetchPageIfNeeded(state, options, action);
  }
  if (!options.cache) {
    return true;
  }
  const lastUpdated = moment(state.lastUpdated);
  if (
    lastUpdated.isValid() &&
    lastUpdated.add(options.cacheDuration, 'seconds').isBefore(moment())
  ) {
    return true;
  }
  if (state.payload === undefined) {
    return true;
  }
  return state.didInvalidate;
};

export const getShouldFetchKeys = (
  state: any,
  keys: any,
  options: Object,
  action: Object,
) => {
  if (keys && keys.constructor === Array) {
    const shouldFetchKeys = [];
    for (let i = 0; i < keys.length; i += 1) {
      const key = keys[i];
      if (shouldFetchIfNeeded(state[key], options, action)) {
        shouldFetchKeys.push(key);
      }
    }
    return shouldFetchKeys;
  }
  if (shouldFetchIfNeeded(state[keys], options, action)) {
    return keys;
  }
  return undefined;
};

const defaultOptions = {
  cache: true,
  cacheDuration: 300,
  paging: false,
  handleParamsPromiseReject: true,
  handleParamsPromiseResolve: true,
};

export const createFetchIfNeededEpic = ({
  ducks,
  options,
}: FetchIfNeededEpicParam) => {
  const { requestTypes, requestActions, selector } = ducks;
  return (action$: any, store: any) =>
    action$
      .ofType(requestTypes.FETCH)
      .filter(action =>
        shouldFetchIfNeeded(selector(store.getState()), options, action),
      )
      .map(action =>
        requestActions.request({
          ...action.params,
          page: get(selector(store.getState()), 'page'),
        }),
      );
};

const shouldContinueFetch = (shouldFetchKeys) => {
  if (shouldFetchKeys && shouldFetchKeys.constructor === Array) {
    return shouldFetchKeys.length;
  }
  return !!shouldFetchKeys;
};

export const createFetchByKeyIfNeededEpic = ({
  ducks,
  mapActionToKey,
  restoreFetchableKeyToAction,
  options,
}: FetchByKeyIfNeededEpicParam) => {
  const { requestTypes, requestActions, selector } = ducks;
  return (action$: any, store: any) =>
    action$
      .ofType(requestTypes.FETCH)
      .map((_action) => {
        const action = {
          ..._action,
        };
        const keys = mapActionToKey(action);
        const shouldFetchKeys = getShouldFetchKeys(
          selector(store.getState()),
          keys,
          options,
          action,
        );
        if (shouldContinueFetch(shouldFetchKeys)) {
          if (restoreFetchableKeyToAction) {
            restoreFetchableKeyToAction(action, shouldFetchKeys);
          }
          action.shouldFetch = true;
        }
        return action;
      })
      .filter(action => action.shouldFetch)
      .map((action) => {
        if (get(options, 'paging')) {
          const result = get(
            selector(store.getState()),
            mapActionToKey(action),
          );
          const page = get(result, 'page') || 0;
          return requestActions.request({
            ...action.params,
            page,
          });
        }
        return requestActions.request(action.params);
      });
};

export const createRequestEpic = ({
  ducks,
  api,
  options,
}: RequestEpicParam) => {
  const { requestTypes, requestActions } = ducks;
  return (action$: any, store: any) =>
    action$.ofType(requestTypes.REQUEST).mergeMap(action =>
      Observable.fromPromise(api(action.params, store))
        .map((data) => {
          if (
            get(action, 'params.resolve') && options.handleParamsPromiseResolve
          ) {
            action.params.resolve();
          }
          return requestActions.success(data, action.params);
        })
        .catch((error) => {
          console.error(error);
          console.error(JSON.stringify(error));
          if (
            get(action, 'params.reject') && options.handleParamsPromiseReject
          ) {
            action.params.reject();
          }
          return Observable.of(requestActions.failure(error, action.params));
        }),
    );
};

export const createRequestIfNeededEpic = ({
  ducks,
  api,
  options,
}: RequestEpicParam) => {
  const mergeOptions = {
    ...defaultOptions,
    ...options,
  };
  const fetchItemsIfNeededEpic = createFetchIfNeededEpic({
    ducks,
    options: mergeOptions,
  });
  const requestEpic = createRequestEpic({ ducks, api, options: mergeOptions });
  return combineEpics(fetchItemsIfNeededEpic, requestEpic);
};

export const createRequestByKeyIfNeededEpic = ({
  ducks,
  api,
  mapActionToKey,
  restoreFetchableKeyToAction,
  options = defaultOptions,
}: RequestByKeyEpicParam) => {
  const mergeOptions = {
    ...defaultOptions,
    ...options,
  };
  const fetchByKeyIfNeededEpic = createFetchByKeyIfNeededEpic({
    ducks,
    mapActionToKey,
    restoreFetchableKeyToAction,
    options: mergeOptions,
  });
  const requestEpic = createRequestEpic({ ducks, api, options: mergeOptions });
  return combineEpics(fetchByKeyIfNeededEpic, requestEpic);
};

type CacheEvictProps = {
  conditionType: Array<string> | string,
  ducks: Ducks,
  filter?: Function
};

export const createCacheEvictEpic = ({
  conditionType,
  ducks,
  filter,
}: CacheEvictProps) => (action$: any, store: any) =>
  action$
    .filter((action) => {
      if (action.type === conditionType) {
        return true;
      }
      return isArray(conditionType) && conditionType.indexOf(action.type) > -1;
    })
    .filter(() => {
      if (filter) {
        return filter(store.getState());
      }
      return get(ducks.selector(store.getState()), 'payload') !== undefined;
    })
    .mergeMap(() =>
      Observable.of(
        ducks.requestActions.clear(),
        ducks.requestActions.request(),
      ),
    );
<|endoftext|>


<commit_before>var userid;
//arrays gebruikt voor autocompletes en selecteren waarde hiervan.
var students = [];
var studentsid = [];

// View model for the page
function pageViewModel(gvm) {
    // Page specific i18n bindings
    gvm.app  = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("WorksheetNameRapport");}, gvm);
    gvm.title = ko.computed(function (){i18n.setLocale(gvm.lang()); return gvm.app() + ' - ' + i18n.__("HomeRapportTitle");}, gvm);
    gvm.projectname = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("ProjectRapportName");}, gvm);
    gvm.homeManual = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("WorksheetRapportManual");}, gvm);

    gvm.tabledata = ko.observableArray([]);

    // Table i18n bindings
    gvm.courseID = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("courseID");}, gvm);
    gvm.courseName = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("courseName");}, gvm);
    gvm.volgStatus = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("volgStatus");}, gvm);
    gvm.courseAction = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("courseAction");}, gvm);

    //get all students
    function getAllStudents() {
        students = [];
        studentsid = [];
        $.getJSON('/api/allstudents', function(data) {
            $.each(data, function(i, item) {
                students.push(item.firstname + " " + item.lastname);
                studentsid.push(item.id);
            });
        });
        return students;
    }

    function initPage() {
        $("#errormessage").hide();

        $('#studentsComplete').autocomplete({ source: getAllStudents() });

        $.getJSON('/api/currentuser', function (data) {
            viewModel.userId = data.id;
            viewModel.updateCourseRapport();
        });
    }
}
<commit_msg>manageStudentPage Maken students ophalen<commit_after>var userid;
//arrays gebruikt voor autocompletes en selecteren waarde hiervan.
var students = [];
var studentsid = [];

// View model for the page
function pageViewModel(gvm) {
    // Page specific i18n bindings
    gvm.app = ko.computed(function () {
        i18n.setLocale(gvm.lang());
        return i18n.__("WorksheetNameRapport");
    }, gvm);
    gvm.title = ko.computed(function () {
        i18n.setLocale(gvm.lang());
        return gvm.app() + ' - ' + i18n.__("HomeRapportTitle");
    }, gvm);
    gvm.projectname = ko.computed(function () {
        i18n.setLocale(gvm.lang());
        return i18n.__("ProjectRapportName");
    }, gvm);
    gvm.homeManual = ko.computed(function () {
        i18n.setLocale(gvm.lang());
        return i18n.__("WorksheetRapportManual");
    }, gvm);

    gvm.tabledata = ko.observableArray([]);

    // Table i18n bindings
    gvm.courseID = ko.computed(function () {
        i18n.setLocale(gvm.lang());
        return i18n.__("courseID");
    }, gvm);
    gvm.courseName = ko.computed(function () {
        i18n.setLocale(gvm.lang());
        return i18n.__("courseName");
    }, gvm);
    gvm.volgStatus = ko.computed(function () {
        i18n.setLocale(gvm.lang());
        return i18n.__("volgStatus");
    }, gvm);
    gvm.courseAction = ko.computed(function () {
        i18n.setLocale(gvm.lang());
        return i18n.__("courseAction");
    }, gvm);

    function getAllTeachers() {
        teachers = [];
        teachersid = [];
        $.getJSON('/api/getteacherrapport', function (data) {
            $.each(data, function (i, item) {
                teachers.push(item.firstname + " " + item.lastname);
                teachersid.push(item.id);
            });
        });
        return teachers;
    }

    function getAllStudentLists() {
        studentLists = [];
        studentListsid = [];
        $.getJSON('/api/studentlistsrapporten/' + userid, function (data) {
            $.each(data, function (i, item) {
                studentLists.push(item.name);
                studentListsid.push(item.id);
            });
        });
        return studentLists;
    }

    function getTeacherid() {

        // functie voor het opsplitsen van naam indien gewenst.
        // console.log(getTeacherID($('#teachersComplete').val().substr(0,$('#teachersComplete').val().indexOf(' ')), $('#teachersComplete').val().substr($('#teachersComplete').val().indexOf(' ')+1)));

        var i = 0;
        var teacher = 0;
        teachers.forEach(function (entry) {
            if (new String(entry).valueOf() == new String($('#teachersComplete').val()).valueOf()) {
                teacher = teachersid[i];
            }
            i += 1;
        });
        return teacher
    }

    function getGroupid() {
        var i = 0;
        var studlijst;
        studentLists.forEach(function (entry) {
            if (new String(entry).valueOf() == new String($('#studentListComplete').val()).valueOf()) {
                studlijst = (studentListsid[i]);
            }
            i += 1;
        });
        return studlijst;
    }

    function addGroup(courseid, teacherid, studlijstid) {
        //TODO if teacher or studlijst = 0 dan bestaat deze niet!
        //TODO momenteel niet mogelijk om meer als 1 maal zelfde velden in te voeren maar geen foutboodschap

        $.ajax({
            url: "/api/coursecouple/" + courseid + "/" + studlijstid + "/" + teacherid,
            type: "POST",
            data: {'course': courseid, 'teacher': teacherid, 'studentlist': teacherid},
            success: function (data) {
                loadTablePage(1, courseid);
                //callback(true);
            },
            error: function (data) {
                console.log('Failed to add new student');
                //callback(false);
            }
        });
    }

    /*
     * Load page of the table
     */
    function loadTablePage(pagenr, course) {
        $.getJSON('/api/getStudentGroupTeacherByCourseID/page/' + pagenr + '/' + course, function (data) {

            /* Clear current table page */
            viewModel.clearTable();

            // Load table data
            $.each(data.data, function (i, item) {

                viewModel.addTableData(item.studid, item.userid, item.name, item.firstname + " " + item.lastname, item.id);

            });

            //TODO pagers doen werken
            //Momenteel wordt enkel alles geselecteerd met LIMIT 0,20
            //Maar de pagers zelf blijven dissabled waardoor het ook niet mogelijk is LIMIT 21,40 op te vragen.
            /* Let previous en next buttons work */

            if (data.prev == "none") {
                $('#pager-prev-btn').addClass('disabled');
            } else {
                $('#pager-prev-btn').removeClass('disabled');
                $('#pager-prev-btn a').click(function () {
                    loadTablePage(data.prev);
                });
            }

            if (data.next == "none") {
                $('#pager-next-btn').addClass('disabled');
            } else {
                $('#pager-next-btn').removeClass('disabled');
                $('#pager-next-btn a').click(function () {
                    loadTablePage(data.next);
                });
            }

            // Number of pager buttons
            var numItems = $('.pager-nr-btn').length;

            /* Calculate for the pager buttons */
            var lowPage = Math.floor(pagenr / numItems) + 1;

            $('.pager-nr-btn').each(function () {
                /* calculate current page number */
                var thispagenr = lowPage++;

                /* Add the page number */
                $(this).html('' + thispagenr + '');

                /* Add active class to current page */
                if (thispagenr == pagenr) {
                    $(this).addClass('active');
                } else {
                    $(this).removeClass('active');
                }

                /* Disable inactive classes and bind handlers to active classes */
                if (thispagenr > data.pagecount) {
                    $(this).addClass('disabled');
                } else {
                    /* Add click listener for button */
                    $(this).click(function () {
                        loadTablePage(thispagenr, course);
                    });
                }
            });
        });
    }

    /*
     * Delete item from table given the id.
     */
    function deleteTableItem(id, tblOject) {
        showYesNoModal("Bent u zeker dat u dit item wil verwijderen? \r\n Let op: verwijderde items blijven in het systeem en kunnen weer actief gezet worden door een administrator. \r\n Gelieve de administrator te contacteren om een vak definitief te verwijderen.", function (val) {
            if (val) {
                //courseid = $('#projectHeader').attr("data-value");
                //studielijstid = "studid " + id.substr(0,id.indexOf('-'));
                //teacherid = "userid " + id.substr(id.indexOf('-')+1);

                $.ajax({
                    url: "/api/setInactiveCourseStudlistCouple/" + $('#projectHeader').attr("data-value") + '/' + id.substr(0, id.indexOf('-')) + '/' + id.substr(id.indexOf('-') + 1),
                    type: "DELETE",
                    success: function () {
                        viewModel.tabledata.remove(tblOject);
                    }
                });

            }
        });
    }

    function initPage() {
        $('#addGroupForm').hide();

            $('#studentsComplete').autocomplete({source: getAllTeachers()});

        $('#addGroupBtn').click(function () {
            addGroup($('#projectHeader').attr("data-value"), getTeacherid(), getGroupid());

            $('#studentListComplete').val("");
            $('#teachersComplete').val("");

            loadTablePage(1, $('#projectHeader').attr("data-value"));

            $('addGroupForm').hide();
        });

        $.getJSON('/api/currentuser', function (data) {
            viewModel.userId = data.id;
            userid = data.id;
        });
        loadTablePage(1, $('#projectHeader').attr("data-value"));
    }
}<|endoftext|>


<commit_before>'use strict';

const User = use('App/Model/User');
const Anime = use('App/Model/Anime');
const UserService = use("App/Services/UserService");
const _ = require('lodash');

const statusTypes = {
  planning: "Planning",
  watching: "Watching",
  on_hold: "On Hold",
  completed: "Completed",
  dropped: "Dropped",
  ignored: "Ignored"
};

class UserController {
  *library(request, response) {
    const userAnime = (yield request.user.anime().fetch()).value();

    const allAnime = (yield Anime.all()).value();
    const availableAnime = _.differenceBy(allAnime, userAnime, 'id');

    yield response.sendView('library', {
      user: request.user.attributes,
      user_anime: userAnime,
      available_anime: availableAnime,
      status_types: statusTypes
    })
  }

  *library_edit_view(request, response) {
    const userAnimeCol = yield request.user.anime().fetch();
    const userAnime = userAnimeCol.value();

    const allAnime = (yield Anime.all()).value();
    const availableAnime = _.differenceBy(allAnime, userAnime, 'id');

    let currentAnime;
    const animeId = request.param('id');
    if (animeId) {
      currentAnime = userAnimeCol.find('id', parseInt(animeId));
    }

    yield response.sendView('library_edit', {
      user: request.user.attributes,
      available_anime: availableAnime,
      status_types: statusTypes,
      current_anime: currentAnime
    })
  }

  *library_save(request, response) {
    const params = request.all();
    const animeId = params.anime;

    const userAnimeCol = yield request.user.anime().fetch();

    if (userAnimeCol.find('id', parseInt(animeId))) {
      yield request.user.anime().detach(animeId)
    }
    yield request.user.anime().attach(animeId, {status: params.status, rating: params.rating});
    yield request.user.update();
    yield UserService.rebuildRecommendations(request.user);

    response.redirect('/library')
  }

  *library_remove(request, response) {
    const params = request.all();
    yield request.user.anime().detach(params.id);
    yield request.user.update();

    response.redirect('/library')
  }

  *user_profile(request, response) {
    const userAnime = yield request.user.anime().fetch();
    const watching = userAnime.filter(function (anime) {
      return anime.status === 'watching'
    }).value();
    yield response.sendView('user_profile', {user: request.user.attributes, watching: watching})
  }
}

module.exports = UserController;
<commit_msg>Update recommendations on library remove<commit_after>'use strict';

const User = use('App/Model/User');
const Anime = use('App/Model/Anime');
const UserService = use("App/Services/UserService");
const _ = require('lodash');

const statusTypes = {
  planning: "Planning",
  watching: "Watching",
  on_hold: "On Hold",
  completed: "Completed",
  dropped: "Dropped",
  ignored: "Ignored"
};

class UserController {
  *library(request, response) {
    const userAnime = (yield request.user.anime().fetch()).value();

    const allAnime = (yield Anime.all()).value();
    const availableAnime = _.differenceBy(allAnime, userAnime, 'id');

    yield response.sendView('library', {
      user: request.user.attributes,
      user_anime: userAnime,
      available_anime: availableAnime,
      status_types: statusTypes
    })
  }

  *library_edit_view(request, response) {
    const userAnimeCol = yield request.user.anime().fetch();
    const userAnime = userAnimeCol.value();

    const allAnime = (yield Anime.all()).value();
    const availableAnime = _.differenceBy(allAnime, userAnime, 'id');

    let currentAnime;
    const animeId = request.param('id');
    if (animeId) {
      currentAnime = userAnimeCol.find('id', parseInt(animeId));
    }

    yield response.sendView('library_edit', {
      user: request.user.attributes,
      available_anime: availableAnime,
      status_types: statusTypes,
      current_anime: currentAnime
    })
  }

  *library_save(request, response) {
    const params = request.all();
    const animeId = params.anime;

    const userAnimeCol = yield request.user.anime().fetch();

    if (userAnimeCol.find('id', parseInt(animeId))) {
      yield request.user.anime().detach(animeId)
    }
    yield request.user.anime().attach(animeId, {status: params.status, rating: params.rating});
    yield request.user.update();
    yield UserService.rebuildRecommendations(request.user);

    response.redirect('/library')
  }

  *library_remove(request, response) {
    const params = request.all();
    yield request.user.anime().detach(params.id);
    yield request.user.update();
    yield UserService.rebuildRecommendations(request.user);

    response.redirect('/library')
  }

  *user_profile(request, response) {
    const userAnime = yield request.user.anime().fetch();
    const watching = userAnime.filter(function (anime) {
      return anime.status === 'watching'
    }).value();
    yield response.sendView('user_profile', {user: request.user.attributes, watching: watching})
  }
}

module.exports = UserController;
<|endoftext|>